Spring boot HATEOAS not automatically adding links to related resources - java

Links are not automatically being provided for resources when using HATEOAS to fetch resource collections.
When fetching a collection of ThreadResource with '/forum/threads', the response is:
{
"_embedded": {
"threadList": [
{
"posts": [
{
"postText": "This text represents a major breakthrough in textual technology.",
"thread": null,
"comments": [],
"thisId": 1
},
{
"postText": "This text represents a major breakthrough in textual technology.",
"thread": null,
"comments": [],
"thisId": 2
}
],
"createdBy": "admin",
"updatedBy": null,
"thisId": 1
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/forum/threads?page=0&size=10"
}
},
"page": {
"size": 10,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
I was expecting a JSON array of posts (instead of links to associated posts collection), like below:
{
"_embedded": {
"threadList": [
{
"createdBy": "admin",
"updatedBy": null,
"thisId": 1,
"_links": {
"posts": {
"href": "http://localhost:8080/forum/threads/1/posts"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/forum/threads?page=0&size=10"
}
},
"page": {
"size": 10,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
I could manually build and add links in ResourceProcessor implementation classes and exclude the collection from being rendered using #JsonIgnore, but I have never had to do this before. What I am doing wrong?
The relevant classes are provided below. Thanks in advance!
#Entity
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany
private List<Post> posts;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_by")
private String updatedBy;
public Thread() { }
#PrePersist
public void prePersist() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
posts = new ArrayList<>();
createdBy = auth.getName();
}
#PreUpdate
public void preUpdate() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
updatedBy = auth.getName();
}
public void submitPost(Post newPost) {
posts.add(newPost);
}
public Long getThisId() {
return id;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
private Thread thread;
#OneToMany
private List<Comment> comments;
public Post() { }
}
public class ThreadResource extends ResourceSupport {
private List<PostResource> postResources;
private String createdBy;
private String updatedBy;
public ThreadResource() {
}
}
public class PostResource extends ResourceSupport {
private String postText;
private ThreadResource threadResource;
private List<CommentResource> commentResources;
public PostResource() { }
#Component
public class PostResourceAssembler extends ResourceAssemblerSupport<Post, PostResource> {
public PostResourceAssembler() {
super(PostController.class, PostResource.class);
}
#Override
public PostResource toResource(Post entity) {
PostResource resource = super.createResourceWithId(entity.getThisId(), entity);
resource.setPostText(entity.getPostText());
return resource;
}
}
#Component
public class ThreadResourceAssembler extends ResourceAssemblerSupport<Thread, ThreadResource> {
private PostResourceAssembler postResourceAssembler;
public ThreadResourceAssembler(PostResourceAssembler postResourceAssembler) {
super(ThreadController.class, ThreadResource.class);
this.postResourceAssembler = postResourceAssembler;
}
#Override
public ThreadResource toResource(Thread entity) {
ThreadResource resource = super.createResourceWithId(entity.getThisId(), entity);
List<Post> posts = entity.getPosts();
List<PostResource> postResources = new ArrayList<>();
posts.forEach((post) -> postResources.add(postResourceAssembler.toResource(post)));
resource.setPostResources(postResources);
return resource;
}
}
#RestController
public class PostController {
private PostService postService;
#Autowired
public PostController(PostService postService) {
this.postService = postService;
}
#GetMapping("/forum/threads/{threadId}/posts/{postId}")
public ResponseEntity<Resource<Post>> getPost(#PathVariable long threadId, #PathVariable long postId) {
Post post = postService.fetchPost(postId)
.orElseThrow(() -> new EntityNotFoundException("not found thread " + postId));
Link selfLink = linkTo(PostController.class).slash(postId).withSelfRel();
post.add(selfLink);
return ResponseEntity.ok(new Resource<>(post));
}
#GetMapping
public ResponseEntity<PagedResources<Resource<Post>>> getPosts(PagedResourcesAssembler<Post> pagedResourcesAssembler) {
Pageable pageable = new PageRequest(0, 10);
Page<Post> posts = postService.fetchAllPosts(pageable);
PagedResources<Resource<Post>> resources = pagedResourcesAssembler.toResource(posts);
return ResponseEntity.ok(resources);
}
#PostMapping("/forum/threads/{threadId}/posts")
public HttpEntity<?> submitPost(#PathVariable long threadId) throws URISyntaxException {
Post post = postService.submitPost(threadId, new Post());
if (post != null) {
Link selfLink = linkTo(methodOn(PostController.class).submitPost(threadId)).slash(post.getThisId()).withSelfRel();
post.add(selfLink);
return ResponseEntity.created(new URI(selfLink.getHref())).build();
}
return ResponseEntity.status(500).build();
}
}
#RestController
public class ThreadController {
private ThreadService threadService;
private ThreadResourceAssembler threadResourceAssembler;
#Autowired
public ThreadController(ThreadService threadService,
ThreadResourceAssembler threadResourceAssembler) {
this.threadService = threadService;
this.threadResourceAssembler = threadResourceAssembler;
}
#GetMapping("/forum/threads/{threadId}")
public ResponseEntity<ThreadResource> getThread(#PathVariable long threadId) {
Thread thread = threadService.fetchThread(threadId)
.orElseThrow(() -> new EntityNotFoundException("not found thread " + threadId));
ThreadResource threadResource = threadResourceAssembler.toResource(thread);
return ResponseEntity.ok(threadResource);
}
#GetMapping("/forum/threads")
public ResponseEntity<PagedResources<Resource<ThreadResource>>> getThreads(PagedResourcesAssembler pagedResourcesAssembler) {
Pageable pageable = new PageRequest(0, 10);
Page<Thread> threads = threadService.fetchAllThreads(pageable);
PagedResources pagedResources = pagedResourcesAssembler.toResource(threads);
return ResponseEntity.ok(pagedResources);
}
#PostMapping("/forum/threads")
public HttpEntity<?> createThread() {
Thread thread = threadService.createThread();
return ResponseEntity.ok(thread);
}
#DeleteMapping("/forum/threads/{threadId}")
public HttpEntity<?> deleteThread(#PathVariable long threadId) {
Thread thread = threadService.fetchThread(threadId)
.orElseThrow(() -> new EntityNotFoundException("not found thread" + threadId));
threadService.closeThread(thread);
return ResponseEntity.ok().build();
}
}

Related

One to Many JPA relationship with 3 entities produces error IllegalArgumentException Entity must not be null

I am trying to create a schema where I have 3 tables, Customer, Orders, and Products that have One to Many relationships. For example, 1 Customer can have many Orders and 1 Order can have many Products (is this possible?). I am able to add a Customer that contains a list of Orders through my post request in postman to satisfy the relationship. However, I cannot do the same for adding a list of Products to an Order. I am getting the error in OrderResource java.lang.IllegalArgumentException: Entity must not be null.. Why is my OrderResource reading a null entity when trying to post a new Order with a list of Products? How can I fix this? Thanks for any help in advance.
HTTP Get request from CustomerResource that shows an empty "products" list in "orders"
{
"id": 1,
"firstName": "Charlie",
"lastName": "Madge",
"email": "cmadge0#fc2.com",
"address": "21556 Arizona Crossing",
"password": "l7QFUwG",
"orders": [
{
"id": 1,
"tracking": "123456789",
"date": "2012-04-23T18:25:43.000+00:00",
"total": "100",
"quantity": "4",
"payment": "Paypal",
"products": []
},
{
"id": 2,
"tracking": "987654321",
"date": "2022-04-23T18:25:43.000+00:00",
"total": "90",
"quantity": "3",
"payment": "Visa",
"products": []
}
]
}
HTTP Post request I used for /assignProducts endpoint which produced the internal server error status 500
{
"orders": {
"id": "1",
"tracking": "123456789",
"date": "2012-04-23T18:25:43.511Z",
"total": "100",
"quantity": "4",
"payment": "Paypal",
"products": [
{
"title": "abc",
"price": "10",
"image": "fdfhsdfh"
}
]
}
}
Orders.java
#Entity
public class Orders implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
private String tracking;
private Date date;
private String total;
private String quantity;
private String payment;
#JsonIgnore
#OneToMany(targetEntity = Products.class, cascade = CascadeType.ALL)
#JoinColumn(name = "op_fk", referencedColumnName = "id")
private List<Products> products;
public Orders() { }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTracking() { return tracking; }
public void setTracking(String tracking) { this.tracking = tracking; }
public Date getDate() { return date; }
public void setDate(Date date) { this.date = date; }
public String getTotal() { return total; }
public void setTotal(String total) { this.total = total; }
public String getQuantity() { return quantity; }
public void setQuantity(String quantity) { this.quantity = quantity; }
public String getPayment() { return payment; }
public void setPayment(String payment) { this.payment = payment; }
public List<Products> getProducts() { return products; }
public void setProducts(List<Products> products) { this.products = products; }
}
Products.java
#Entity
public class Products implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, updatable = false)
private Long id;
private String title;
private String price;
private String image;
public Products() { }
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getPrice() { return price; }
public void setPrice(String price) { this.price = price; }
public String getImage() { return image; }
public void setImage(String image) { this.image = image; }
}
OrdersResource.java
#RestController
#RequestMapping("/orders")
public class OrdersResource {
#Autowired
private final OrdersService ordersService;
public OrdersResource(OrdersService ordersService) { this.ordersService = ordersService; }
#GetMapping("/all")
public ResponseEntity<List<Orders>> getAllOrders(){
List<Orders> orders = ordersService.findAllOrders();
return new ResponseEntity<>(orders, HttpStatus.OK);
}
#GetMapping("/find/{id}")
public ResponseEntity<Orders> getOrderById(#PathVariable("id") Long id){
Orders orders = ordersService.findOrderById(id);
return new ResponseEntity<>(orders, HttpStatus.OK);
}
#PostMapping("/add")
public ResponseEntity<Orders> addOrder(#RequestBody Orders order){
Orders newOrder = ordersService.addOrder(order);
return new ResponseEntity<>(newOrder, HttpStatus.CREATED);
}
#PostMapping("/assignProducts")
public ResponseEntity<Orders> assignProduct(#RequestBody AssignProductRequest request){
Orders newOrder = ordersService.addOrder(request.getOrder());
return new ResponseEntity<>(newOrder, HttpStatus.CREATED);
}
#PostMapping("/update")
public ResponseEntity<Orders> updateOrder(#RequestBody AssignProductRequest request){
Orders updateOrder = ordersService.updateOrder(request.getOrder());
return new ResponseEntity<>(updateOrder, HttpStatus.OK);
}
#Transactional
#DeleteMapping("/delete/{id}")
public ResponseEntity<?> deleteOrder(#PathVariable("id") Long id){
ordersService.deleteOrderById(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
OrdersService.java
#Service
public class OrdersService {
private final OrdersRepo ordersRepo;
#Autowired
public OrdersService(OrdersRepo ordersRepo) { this.ordersRepo = ordersRepo; }
public Orders addOrder(Orders order){ return ordersRepo.save(order); }
public List<Orders> findAllOrders(){ return ordersRepo.findAll(); }
public Orders updateOrder(Orders order){ return ordersRepo.save(order); }
public Orders findOrderById(Long id){
return ordersRepo.findOrderById(id)
.orElseThrow(()-> new OrderNotFoundException("Order by id " + id + " was not found"));
}
public void deleteOrderById(Long id){ ordersRepo.deleteOrderById(id); }
}
AssignProductsRequest.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class AssignProductRequest { private Orders order; }
ProductsResource.java
#RestController
#RequestMapping("/products")
public class ProductsResource {
#Autowired
private final ProductsService productsService;
public ProductsResource(ProductsService productsService) { this.productsService = productsService; }
#GetMapping("/all")
public ResponseEntity<List<Products>> getAllProducts(){
List<Products> products = productsService.findAllProducts();
return new ResponseEntity<>(products, HttpStatus.OK);
}
#GetMapping("/find/{id}")
public ResponseEntity<Products> getProductById(#PathVariable("id") Long id){
Products product = productsService.findProductById(id);
return new ResponseEntity<>(product, HttpStatus.OK);
}
#PostMapping("/add")
public ResponseEntity<Products> addProduct(#RequestBody Products product){
Products newProduct = productsService.addProduct(product);
return new ResponseEntity<>(newProduct, HttpStatus.CREATED);
}
#PostMapping("/update")
public ResponseEntity<Products> updateProduct(#RequestBody ProductsRequest request){
Products updateProduct = productsService.updateProduct(request.getProduct());
return new ResponseEntity<>(updateProduct, HttpStatus.OK);
}
#Transactional
#DeleteMapping("delete/{id}")
public ResponseEntity<?> deleteProduct(#PathVariable("id") Long id){
productsService.deleteProduct(id);
return new ResponseEntity<>(HttpStatus.OK);
}
}
ProductsService.java
#Service
public class ProductsService {
private final ProductsRepo productsRepo;
#Autowired
public ProductsService(ProductsRepo productsRepo) { this.productsRepo = productsRepo; }
public Products addProduct(Products product){ return productsRepo.save(product); }
public Products updateProduct(Products products){ return productsRepo.save(products); }
public List<Products> findAllProducts(){ return productsRepo.findAll(); }
public Products findProductById(Long id){
return productsRepo.findProductById(id)
.orElseThrow(()-> new ProductNotFoundException("Product by id " + id + " was not found"));
}
public void deleteProduct(Long id){
productsRepo.deleteProductById(id);
}
}
OrdersRepo.java
public interface OrdersRepo extends JpaRepository<Orders, Long> {
void deleteOrderById(Long id);
Optional<Orders> findOrderById(Long id);
}
ProductsRepo.java
public interface ProductsRepo extends JpaRepository<Products, Long> {
void deleteProductById(Long id);
Optional<Products> findProductById(Long id);
}

why don't you see the "keys" in the json?

when I enter postman, I get the json, but without his "keys" why? Maybe I'm making a mistake and I haven't noticed. Some help please.
I am using a stored procedure to be able to do a crud.
this is the json that shows me postman. Shows me without his "key"
{
"data": [
[
1,
"aaa",
"aaa#gmail.com"
],
[
2,
"bbb",
"bbb#gmail.com"
],
[
3,
"ccc",
"ccc#gmail.com"
]
]
}
I would like to get something like this.
{
"data": [
{
userCod: 1,
userName: "aaa",
userEmail: "aaa#gmail.com"
},
{
userCod: 2,
userName: "bbb",
userEmail: "bbb#gmail.com"
},
{
userCod: 3,
userName: "ccc",
userEmail: "ccc#gmail.com"
}
]
}
I leave the code
public class ApiResponse {
private List<UserTest> data;
public List<UserTest> getData() {
return data;
}
public void setData(List<UserTest> data) {
this.data = data;
}
}
#Entity
#Table(name = "tbUsers")
public class UserTest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userCod")
private Long id;
#Column(name = "userName")
private String name;
#Column(name = "userEmail")
private String email;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
#Repository
public class ClienteDaoImpl implements IClienteDao{
#Autowired
private EntityManager em;
#SuppressWarnings("unchecked")
#Override
public ApiResponse mntUsers(int op) {
ApiResponse api = new ApiResponse();
Session session = em.unwrap(Session.class);
ProcedureCall call = session.createStoredProcedureCall("sp_MntUser");
call.registerParameter(1, Integer.class, ParameterMode.IN);
call.setParameter(1, op);
call.execute();
api.setData(call.getResultList());
return api;
}
}
#RestController
#RequestMapping(value = "/mntUsers")
public class ClienteController {
#Autowired
private ClienteServiceImpl serviceImpl;
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> CrudUsers(#RequestParam(value = "option", required = true) Integer op) {
return new ResponseEntity<>(serviceImpl.mntUsers(op),HttpStatus.OK);
}
}
Create a method called getCollectionType
public static <T, C extends Collection<T>> C getCollectionType(Iterable<?> from, C to, Class<T> listClass) {
for (Object item: from) {
to.add(listClass.cast(item));
}
return to;
}
Then use it on the following line:
api.setData(getCollectionType(call.getResultList(),
new ArrayList<UserTest>(),
UserTest.class));

Elasticsearch Spring repository search using multiple fields

I have a Spring Boot application connected to an Elasticsearch instance containing this sample data. I am currently able to search for a specific field, but the moment I add the second one in the request, I don't have any results (each of the fields are fine on their own). How can I get the correct results?
Here are my classes:
#Document(indexName = "bank", type = "account", replicas = 0)
public class Account {
#Id
private String id;
private long accountNumber;
private long balance;
private String firstname;
private String lastname;
private long age;
private String gender;
private String address;
private String employer;
private String email;
private String city;
private String state;
}
public interface AccountRepository extends ElasticsearchRepository<Account, String> {
Page<Account> findByGenderAndStateAllIgnoreCase(String gender, String state, Pageable pageable);
}
#Service
public class AccountServiceImpl implements AccountService {
#Autowired
private AccountRepository repository;
#Override
public Account save(Account account) {
return repository.save(account);
}
#Override
public Account findOne(String id) {
return repository.findOne(id);
}
#Override
public Collection<Account> findAll(PageRequest request) {
return repository.findAll(request).getContent();
}
#Override
public Collection<Account> findByGenderAndState(String gender, String state, PageRequest request) {
return repository.findByGenderAndStateAllIgnoreCase(gender, state, request).getContent();
}
}
#Controller
#RequestMapping("/bank")
public class BankController {
#Autowired
private AccountService accountService;
#GetMapping("/accounts")
public
#ResponseBody
Collection<Account> accounts(#RequestParam(name = "gender", required = false, defaultValue = "*") String gender,
#RequestParam(name = "state", required = false, defaultValue = "*") String state,
#RequestParam(name = "page", required = false, defaultValue = "0") int page,
#RequestParam(name = "size", required = false, defaultValue = "10") int size) {
return accountService.findByGenderAndState(gender, state, new PageRequest(page, size));
}
}
Here is the query that is sent to Elasticsearch from Spring Boot:
[
{
"from": 20,
"size": 20,
"query": {
"bool": {
"must": [
{
"query_string": {
"query": "f",
"fields": [
"gender"
],
"default_operator": "and"
}
},
{
"query_string": {
"query": "dc",
"fields": [
"state"
],
"default_operator": "and"
}
}
]
}
}
}
]
Turns out I was querying for the page #1, instead of #0. The results are showing correctly now.

Spring REST Jpa HATEOAS links not being created

I know that very similar questions have been asked here before, but I'm struggling to apply it to my problem.
I've recently started using jpa repositories for my data persistence needs and until now i had been content building HAL links that i wanted. Then i found that if i started using the #JoinTable and #JoinColumn annotations then i could have my links generated for me.
My problem is that when i hit the endpoint for my /posts, i don't get a HAL link for comments in the response.
#Entity
#Table(name="post")
public class Post {
#Id
#Column(name="id")
private #JsonIgnore Long id;
#Column(name="text")
private String text;
#Column(name="sender_id")
private Long senderId;
#Column(name="event_id")
private Long eventId;
protected Post () {};
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public Long getEventId() {
return eventId;
}
public void setEventId(Long eventId) {
this.eventId = eventId;
}
}
#Entity
#Table(name="comment")
public class Comment {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name="id")
private #JsonIgnore Long id;
#Column(name="text")
private String comment;
#JsonIgnore
#Column(name="sender_id")
private Long senderId;
#JsonIgnore
#JoinColumn(name="post_id")
private Long post_id;
#JsonIgnore
#Column(name="deleted")
private Boolean deleted;
#ManyToOne
#JoinTable(name="post")
private Post post;
protected Comment() {};
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public Long getPostId() {
return post_id;
}
public void setPostId(Long postId) {
this.post_id = postId;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post
}
}
#RestController
public class CommentController {
private JdbcOperations jdbc;
private final CommentRepository commentDao;
#Autowired
public CommentController(CommentRepository postDao) {
this.commentDao = postDao;
}
#RequestMapping(value = "/comments", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Comment>>> getPagedList(#RequestParam(value = "user-id", required = true) long userId,
#RequestParam(value = "page-size", required = false, defaultValue = "20") Long pageSize,
#RequestParam(value = "page", required = false, defaultValue = "1") Long pageNum) {
List<Comment> commentsList = (ArrayList) commentDao.findAll();
List<Resource<Comment>> resourceList = new ArrayList<>();
for (Comment comment : commentsList) {
Resource<Comment> postResource = new Resource<>(comment);
postResource.add(linkTo(methodOn(CommentController.class)
.getPagedList(userId, pageSize,pageNum)).withSelfRel());
resourceList.add(postResource);
}
Resources<Resource<Comment>> resources = new Resources<>(resourceList,
linkTo(methodOn(PostController.class)
.getPagedList(userId, pageSize, pageNum)).withSelfRel());
return ResponseEntity.ok(resources);
}
#RestController
public class PostController {
private final PostRepository postDao;
#Autowired
public PostController(PostRepository postDao) {
this.postDao = postDao;
}
#RequestMapping(value = "/posts", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Post>>> getPagedList(#RequestParam(value="user-id", required = true) long userId,
#RequestParam(value = "page-size", required = false, defaultValue = "20") Long pageSize,
#RequestParam(value = "page", required = false, defaultValue = "1") Long pageNum) {
List<Post> modelsList = (ArrayList) postDao.readBydeletedIsFalseOrderByCreated();
List<Resource<Post>> resourceList = new ArrayList<>();
for (Post post : modelsList) {
Resource<Post> resource = new Resource<>(post);
resource.add(linkTo(methodOn(PostController.class)
.getSpecificModel(post.getId())).withSelfRel());
resource.add(linkTo(methodOn(UserController.class).getSpecificModel(post.getSenderId()))
.withSelfRel().withRel("sender"));
if (post.getEventId() != null) {
resource.add(linkTo(methodOn(EventController.class)
.getSpecificModel(post.getEventId())).withSelfRel().withRel("event"));
}
resourceList.add(resource);
}
Resources<Resource<Post>> resources = new Resources<>(resourceList,
linkTo(methodOn(PostController.class)
.getPagedList(userId, pageSize, pageNum)).withSelfRel());
return ResponseEntity.ok(resources);
}
The response i get when i hit the /posts endpoint is this, with no link to comments:
http://localhost:8090/posts?user-id=1
{
"_embedded": {
"postList": [
{
"created": -84330000000,
"text": "second post",
"senderId": 2,
"eventId": null,
"_links": {
"self": {
"href": "http://localhost:8090/posts/2"
},
"sender": {
"href": "http://localhost:8090/users/2"
}
}
},
{
"created": 1286665200000,
"text": "dfgtfy",
"senderId": 1,
"eventId": null,
"_links": {
"self": {
"href": "http://localhost:8090/posts/1"
},
"sender": {
"href": "http://localhost:8090/users/1"
}
}
},
{
"created": 1464735600000,
"text": "third",
"senderId": 1,
"eventId": null,
"_links": {

Spring Data Rest ManytoMany POST

First, let me explain my usecase. It's pretty straight forward. There is a User entity and a Service entity. I have ManytoMany association between User and Service using UserService as the Joined entity (joined table) Initially,there will be some set of users and some set of services. Users can subscribe to any Service at any point of time. In that case, an entry will be added to UserService. But, I am getting null pointer exception when i tried to create a new UserService association. I could create User and Service individually.
My Entities are :
User.java
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="`user`", schema="emm")
public class User implements Serializable {
public User() {
}
#Column(name="id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_USER_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_USER_ID_GENERATOR", strategy="native")
private long id;
#ManyToOne(targetEntity=dao.models.Tenant.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="tenant_id", referencedColumnName="id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Tenant tenant;
#OneToOne(targetEntity=dao.models.Person.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="Person_id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Person person;
#Column(name="password", nullable=true, length=255)
private String password;
#Column(name="email", nullable=false, length=255)
private String email;
#Column(name="status", nullable=true, length=255)
private String status;
#ManyToMany(mappedBy="user", targetEntity=dao.models.TenantGroup.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.TenantGroup> group = new java.util.ArrayList<dao.models.TenantGroup>();
#OneToMany(mappedBy="user", targetEntity=dao.models.UserService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserService> userService = new java.util.ArrayList<dao.models.UserService>();
public void setId(long value) {
this.id = value;
}
public long getId() {
return id;
}
public void setPassword(String value) {
this.password = value;
}
public String getPassword() {
return password;
}
public void setEmail(String value) {
this.email = value;
}
public String getEmail() {
return email;
}
public void setStatus(String value) {
this.status = value;
}
public String getStatus() {
return status;
}
public void setTenant(dao.models.Tenant value) {
this.tenant = value;
}
public dao.models.Tenant getTenant() {
return tenant;
}
public void setPerson(dao.models.Person value) {
this.person = value;
}
public dao.models.Person getPerson() {
return person;
}
public void setGroup(java.util.List<dao.models.TenantGroup> value) {
this.group = value;
}
public java.util.List<dao.models.TenantGroup> getGroup() {
return group;
}
public java.util.List<dao.models.UserService> getUserService() {
return userService;
}
public void setUserService(
java.util.List<dao.models.UserService> userService) {
this.userService = userService;
}
public String toString() {
return String.valueOf(getId());
}
}
Service Entity
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="service", schema="emm")
public class Service implements Serializable {
public Service() {
}
#Column(name="service_id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_SERVICE_SERVICE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_SERVICE_SERVICE_ID_GENERATOR", strategy="native")
private long id;
#Column(name="service_name", nullable=false, length=255)
#org.hibernate.annotations.Index(name="service_service_name")
private String serviceName;
#Column(name="description", nullable=true, length=255)
private String description;
#Column(name="app_key", nullable=false, length=255)
private String appKey;
#Column(name="app_token", nullable=false, length=255)
private String appToken;
#Column(name="learnmoreurl", length=255)
private String learnMoreURL;
#Column(name="trialurl", length=255)
private String trialURL;
#ManyToMany(mappedBy="service", targetEntity=dao.models.Device.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.Device> device = new java.util.ArrayList<dao.models.Device>();
#OneToMany(mappedBy="service", targetEntity=dao.models.ServiceParam.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.ServiceParam> serviceParams = new java.util.ArrayList<dao.models.ServiceParam>();
#OneToMany(mappedBy="service", targetEntity=dao.models.TenantService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST, org.hibernate.annotations.CascadeType.MERGE,
org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.TenantService> tenantService = new java.util.ArrayList<dao.models.TenantService>();
#OneToMany(mappedBy="service", targetEntity=dao.models.UserService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserService> userService = new java.util.ArrayList<dao.models.UserService>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getAppToken() {
return appToken;
}
public void setAppToken(String appToken) {
this.appToken = appToken;
}
public String getLearnMoreURL() {
return learnMoreURL;
}
public void setLearnMoreURL(String learnMoreURL) {
this.learnMoreURL = learnMoreURL;
}
public String getTrialURL() {
return trialURL;
}
public void setTrialURL(String trialURL) {
this.trialURL = trialURL;
}
public java.util.List<dao.models.Device> getDevice() {
return device;
}
public void setDevice(java.util.List<dao.models.Device> device) {
this.device = device;
}
public java.util.List<dao.models.ServiceParam> getServiceParams() {
return serviceParams;
}
public void setServiceParams(
java.util.List<dao.models.ServiceParam> serviceParams) {
this.serviceParams = serviceParams;
}
public java.util.List<dao.models.TenantService> getTenantService() {
return tenantService;
}
public void setTenantService(
java.util.List<dao.models.TenantService> tenantService) {
this.tenantService = tenantService;
}
public java.util.List<dao.models.UserService> getUserService() {
return userService;
}
public void setUserService(
java.util.List<dao.models.UserService> userService) {
this.userService = userService;
}
public String toString() {
return String.valueOf(getId());
}
}
And finally the join entity
UserService.java
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="user_service" ,schema="emm")
public class UserService implements Serializable {
public UserService() {
}
#Column(name="id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_USER_SERVICE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_USER_SERVICE_ID_GENERATOR", strategy="native")
private long id;
#ManyToOne(targetEntity=dao.models.User.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="user_id", referencedColumnName="id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.User user;
#ManyToOne(targetEntity=dao.models.Service.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="service_id", referencedColumnName="service_id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Service service;
#Column(name="param_name", nullable=false)
private String paramName;
#Column(name="param_value", nullable=true)
private String paramValue;
#OneToMany(mappedBy="userService", targetEntity=dao.models.UserServiceToken.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserServiceToken> userServiceToken = new java.util.ArrayList<dao.models.UserServiceToken>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public dao.models.User getUser() {
return user;
}
public void setUser(dao.models.User user) {
this.user = user;
}
public dao.models.Service getService() {
return service;
}
public void setService(dao.models.Service service) {
this.service = service;
}
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public String getParamValue() {
return paramValue;
}
public void setParamValue(String paramValue) {
this.paramValue = paramValue;
}
public java.util.List<dao.models.UserServiceToken> getUserServiceToken() {
return userServiceToken;
}
public void setUserServiceToken(
java.util.List<dao.models.UserServiceToken> userServiceToken) {
this.userServiceToken = userServiceToken;
}
public String toString() {
return String.valueOf(getId());
}
}
Now my issue, GET requests are working properly, But, I get null pointer exception when I try to create a new UserService.
POST : http://localhost:8080/em/api/userServices/
I am trying to associate user 1 with service 2
Request :
{
"paramName": "p1",
"paramValue": "v1",
"service": {
"href": `"http://localhost:8080/em/api/userServices/1/service/2"`
},
"user": {
"href": `"http://localhost:8080/em/api/userServices/1/user/1"`
}
}
Error Messgae :
{
"cause": {
"cause": {
"cause": null,
"message": null
},
"message": "(was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"])"
},
"message": "Could not read JSON: (was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"])"
}
GET http://localhost:8080/em/api/userServices yields me the following output :
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"userServices" : [ {
"paramName" : "p1",
"paramValue" : "v1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices/1"
},
"userServiceToken" : {
"href" : "http://localhost:8080/em/api/userServices/1/userServiceToken"
},
"user" : {
"href" : "http://localhost:8080/em/api/userServices/1/user"
},
"service" : {
"href" : "http://localhost:8080/em/api/userServices/1/service"
}
}
}, {
"paramName" : "pone",
"paramValue" : "vone",
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices/2"
},
"userServiceToken" : {
"href" : "http://localhost:8080/em/api/userServices/2/userServiceToken"
},
"user" : {
"href" : "http://localhost:8080/em/api/userServices/2/user"
},
"service" : {
"href" : "http://localhost:8080/em/api/userServices/2/service"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}
Has anyone successfully implemented ManyToMany association using Spring-data-rest. If so, kindly help me in this regard
I figured out the issue and got it working.
Previously, my request body was:
{
"paramName": "p1",
"paramValue": "v1",
"service": {
"href": "http://localhost:8080/em/api/userServices/1/service/2"
},
"user": {
"href": "http://localhost:8080/em/api/userServices/1/user/1"
}
}
I figured out that it should be the following:
{
"paramName": "p1",
"paramValue": "v1",
"service": "http://localhost:8080/em/api/services/2",
"user": "http://localhost:8080/em/api/users/1"
}
I feel there is still an issue with spring-data-rest. Kindly clarify, if anyone feels otherwise. Even with the fixed request, i was getting null constraint for ServiceId. I figured out in db, the primary key column for service was service_id. Even though, I have the entity mapping properly (My Id property in Service Entity maps properly to service_id in db), it was not working, I had to change the column name to id to get this working.
Spring-Data-Rest should depending upon the Entity mappings for Id right ? If so, then Still there is a bug.
Thanks,
Vivek

Categories