How to update a REST Resource with Spring Data Rest that has a OneToMany and nested a ManyToOne relation - spring

I have a simple Invoice System where I need to create and update Invoices. I'm trying to use Spring Data Rest for that, but from what I get from the docs I really would end up doing a lot of calls to implement an update.
#Entity
class Article {
#Id
#GeneratedValue
Long ID;
#Basic
String name;
}
#Entity
class Invoice {
#Id
#GeneratedValue
Long ID;
#Basic
String customer;
#OneToMany(cascade=CascadeType.ALL)
List<InvoiceItem> items;
}
#Entity
class InvoiceItem {
#Id
#GeneratedValue
Long ID;
#Basic
double amount;
#ManyToOne
private Article article;
}
I'm using Spring Data Rest to expose this model to my web fronend via these Spring Data Rest/JPA Repositories
#RepositoryRestResource(collectionResourceRel = "articles", path = "articles")
interface ArticleJPARepository extends JpaRepository<Article, Long> {}
#RepositoryRestResource(collectionResourceRel = "invoices", path = "invoices")
interface InvoiceJPARepository extends JpaRepository<Invoice, Long> {}
#RepositoryRestResource(collectionResourceRel = "invoiceitems", path = "invoiceitems")
interface InvoiceItemJPARepository extends JpaRepository<InvoiceItem, Long> {}
So let's say I have an update form that looks like this:
where I can do several things:
Update Invoice customer
Change an Article of an existing InvoiceItem
Remove some of the InvoiceItems
Append a new InvoiceItem
From what I get from the Spring Rest Docs is that I need to make several calls now.
Update of the invoice customer I need to PUT /invoices/1
then update n the Article and amount in an InvoiceItem PUT n times PUT /invoiceitems/<x>/
Append m new InvoiceItems m times POST /invoiceitems then POST /invoices/1/items
Delete InvoiceItem 2 DELETE /invoiceitems/2 and then DELETE /invoices/1/items/<2>
Is there a simpler way to realise such an update with Spring Data Rest?

Related

Spring Boot many to many post method not updating data

My User class looks like this :
#Data
#Entity
public class User {
#Id
Long userID;
#ManyToMany(mappedBy = "admins")
private List<ClassRoom> classRooms = new ArrayList<>();
}
And my ClassRoom class like this :
#Data
#Entity
public class ClassRoom {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long classRoomID;
#ManyToMany
#JoinTable(name ="classroom_user",
joinColumns = #JoinColumn(name = "classroom_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> admins = new ArrayList<>();
}
And in my UserController class, I have :
#PostMapping("user/{id}/c")
User addClassRoom(#PathVariable Long id,#RequestBody ClassRoom newClassRoom)
{
logger.debug(repository.findById(id));
return repository.findById(id)
.map(user -> {
user.getClassRooms().add(newClassRoom);
user.setClassRooms(user.getClassRooms());
return repository.save(user);
})
.orElseGet(() -> {
return null;
});
}
And I POST and empty JSON ({}) and I see no change in my users. The Classroom or an empty Classroom doesn't get added in the User.
What is the problem here? How can I resolve this ?
user.getClassRooms().add(newClassRoom); is suffice, user.setClassRooms(user.getClassRooms()); not required.
You will have to perform cascade save operation.List all cascade types explicitly and don't use mappedBy, instead use joincolumns annotation.
Can you paste the logs, please? Is Hibernate doing any insert into your table? Has the database schema been created in the DB correctly? One thing I recommend you to do is to add a custom table name on the top of your User class, using annotations like so: #Table(name = "users"). In most SQL dialects user is a reserved keyword, hence it is recommended to always annotate User class a bit differently, so that Hibernate won't have any problems to create a table for that entity.
IMO you must find classRoom by its id from repository, if it's new, you must create a new entity and save it first. Then assign it to user and save it.
The object you receive from the post method was not created by the entity manager.
After using user.getClassRooms().add(newClassRoom);
We must use userRepository.save(user);

Inject data in json entity with Spring Data REST

I have JPA entity for department:
public class Department {
#Id
#Column(unique = true, nullable = false)
private long id;
#Basic
#Column
#Nationalized
private String name;
#Basic
#Column(length = 400)
#Nationalized
private String branch;
...
}
And REST repository
#RepositoryRestResource(collectionResourceRel = "departments", path = "departments")
public interface DepartmentRestRepository extends PagingAndSortingRepository<Department, Long> {
...
}
Entity's branch field is IDs of entity parent departments separated by space, e.g. 1 2 3.
When I query specific department via /api/departments/ID is it possible to attach to it field like parents with all parent entities queried?
I tryed to add getParents method to entity, but it obviously gave me unneeded parents queries with all entities recursively.
Update 2019.01.17:
As a workaround I splitted Department entity into Department and DepartmentWithParents. I added Department getParents() method to DepartmentWithParents entity and exposed REST API method that returns DepartmentWithParents.
Is there better way?
As a workaround I splitted Department entity into Department and DepartmentWithParents. I added Department getParents() method to DepartmentWithParents entity and exposed REST API method that returns DepartmentWithParents.

Sort feature in association endpoint in Spring Data Rest

I have the following two resources, and their association;
#Table(name = "Item")
#Data
#Entity
public class Item {
#ManyToOne
#JoinColumn(name = "fk_wrapper")
private Wrapper wrapper;
#Id
#GeneratedValue
private String id;
private Integer someValue;
}
and;
#Table(name = "Wrapper")
#Data
#Entity
public class Wrapper {
#Id
#GeneratedValue
private String id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_wrapper")
private List<Item> items;
private String someField;
}
Then, first, I create a Wrapper;
POST http://localhost:8080/wrappers/
{
"someField": "asd"
}
http://localhost:8080/wrappers/1 created, then I create two Item's, linked to this Wrapper;
POST http://localhost:8080/items/
{
"someValue": "5",
"wrapper": "http://localhost:8080/wrappers/1"
}
&
POST http://localhost:8080/items/
{
"someValue": "7",
"wrapper": "http://localhost:8080/wrappers/1"
}
After all this, when I call the endpoint http://localhost:8080/wrappers/1/items, I get the list of these two items, as expected, but what the trouble is that, I cannot seem to have a sorting feature on this endpoint. I seem to be able to sort in http://localhost:8080/items endpoint, but while fetching with association, there doesn't seem to be a sorting feature. Is this lack of sorting is intended, or am I lacking some configuration?
P.S. when I create a custom search method, for example;
#RepositoryRestResource
public interface ItemRepository extends JpaRepository<Item, String> {
List<Item> findByWrapper_Id(#Param("id") String id, Sort sort);
}
Then I can use the sorting with http://localhost:8080/items/search/findByWrapper_Id endpoint, but too ugly imo, considering there is already an auto-generated endpoint.
Spring Data Rest doesn't support sorting on the associations.
You seem to have already found the best way to do what you need, according to the Spring Data Rest team - create a query for fetching the data you need. That will indeed support both pagination and sorting.
The reason why it's not supported has to do with the time when the queries are made to fetch the main resource (before the association endpoints are built) and the facts that the association endpoint makes use of the the loaded entity associations directly and that for supporting sort, a new query would need to be made anyway.
More detailed information can be found here:
https://jira.spring.io/browse/DATAREST-725?focusedCommentId=122244&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-122244
Cheers!

Spring JPA one to many denormalized count field

I have two entities, Books and Comments, in a one to many relationship (one book can have many comments). I want to be able to list books and number of comments about a book. I want it denormalized, meaning the books entity will have a counter that has number of comments for that book, and it will be updated every time a comment is entered (just playing with the concept, no need to discuss about the need of denormalizing here).
I think (correct me if I am wrong) this could be easily done with a trigger in the database (whenever a new comment is created, update a counter in the books table to the corresponding bookId), but for the sake of learning I want to do it through JPA, if it makes sense.
What I have so far: //omitted some annotations, just general info
Boks entity:
#Entity
public class Books {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private String author;
private Long numComments;
// getters and setters...
}
Comments entity:
#Entity
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String comment;
private Long authorId;
private Long bookId;
// getters and setters...
}
Books repository: I added here a query to perform the update
/**
* Spring Data JPA repository for the Books entity.
*/
public interface BooksRepository extends JpaRepository<Books,Long> {
#Modifying
#Query("UPDATE Books v SET v.numComments = v.numComments + 1 WHERE v.id = :bookId")
int updateCounter(#Param("bookId")Long bookId);
}
And now the question: What next? I think I can put the update of the Books entity annotating with #PostPersist a method of the entity Comments, but I have been unsuccessful so far. I can imagine something like this:
#PostPersist //This function in the entity Comments
protected void updateBooks() {
//Likely some call to the repository here that updates the count
// in books the info we have from current entity.
}
Any idea on how to do this? Some best practices about this kind of denormalization in JPA? Better to use the database triggers?
spring not managed your entity classes and your idea is possible but you must inject BooksRepository in enttiy class then stay at you get Nullpointerexception because spring not managed enttiy classes,The reason your BooksRepository not initlaized, try also read this post Bean injection inside a JPA #Entity and anotate entity class #Configurable after
try this
#PostPersist
protected void updateBooks(Comments comment) {
int totalComment = BooksRepository.updateCounter(comment.getBookId());
System.out.println(totalComment); // see totalComment in console
}
but good aprroach in service classes after call updateCounter when insert comment
example in your CommendService : when try a insert commend after call your updateCounter
if(comment.getBookId() != null) //Simple Control
{
CommentRepository.save(comment);
BooksRepository.updateCounter(comment.getBookId());
}

Retrieve entity auto generated Id

I am trying to find a way to retrieve the auto generated Id of an entity that is persisted in the database via cascade. I am using Hibernate 4.1.9, Spring data 1.2 and Spring framework 3.2.1. Here are the entities in question : Location, Home, Room.
Location parent class
#Entity
#Table(name = "location")
#Inheritance(strategy = InheritanceType.JOINED)
public class Location implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "location_id", unique = true)
private long uuid;
// other attributes and methods not relevant
}
Home class extending a Location, referencing a set of Rooms
#Entity
#Table(name = "home")
#Inheritance(strategy = InheritanceType.JOINED)
#PrimaryKeyJoinColumn(name = "home_id")
public class Home extends Location implements Serializable
{
#OneToMany(mappedBy = "containingHome", cascade = {CascadeType.ALL}, orphanRemoval = true)
private Set<Room> rooms;
// other attributes and methods not relevant
}
and finally the Room class referencing a Home object
#Entity
#Table(name = "room")
#PrimaryKeyJoinColumn(name = "room_id")
public class Room extends Location implements Serializable
{
#ManyToOne()
#JoinColumn(name = "home_id")
protected Home containingHome;
// other attributes and methods not relevant
}
I am using Spring data to create Repositories for the entities.
LocationRepository
public interface LocationRepository extends JpaRepository<Location, Long>
{ }
The problem I am having is that I need the id in order to be able to retrieve the different objects from the database and that is generated automatically. The only way I can access the id through the element is if I get the managed object when I save it to the database. But if I try to save each location in turn like so:
Home home = new Home();
home = locationService.save(home) // service that just calls locationRepository.save method
Room bedroom = new Room(home);
bedroom = locationService.save(bedroom);
I get a duplicate entry of room in the database which I think is related to a Hibernate issue https://hibernate.onjira.com/browse/HHH-7404. If I just call
Home home = new Home();
Room bedroom = new Room(home);
locationService.save(home)
there are no doubles but I have no way to retrieve the room object since it was persisted on cascade and its id is 0. Is there a way to solve this without introducing other fields in the location like a unique name that I have to generate myself? Any help is much appreciated.
Edit
If in the last case I have home = locationService.save(home) and then call home.getUuid() I get the right value which is normal I think since I retrieve a managed object. But if I do bedroom.getUuid() I get 0 since bedroom is not managed and so it has not had its id field updated with the value from the database.
Have you tried calling home.getUuid(); (assuming you have a getter for that field) after the persist call?
You might be surprised, but Hibernate (and JPA) will update the in memory copy with the id.

Resources