Using record classes with #RequestBean in Micronaut - java

I am attempting to use Java records with #Valid and #RequestBean in Micronaut and am running into the following compilation error:
/Users/user/IdeaProjects/record_test/src/main/java/com/example/ReadController.java:16:28
java: Parameter of Primary Constructor (or #Creator Method) [id] for type [com.example.ReadBean] has one of #Bindable annotations. This is not supported.
Note1: Primary constructor is a constructor that have parameters or is annotated with #Creator.
Note2: In case you have multiple #Creator constructors, first is used as primary constructor.
My classes are as follows:
ReadBean.java:
package com.example;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.PathVariable;
import javax.validation.constraints.Positive;
#Introspected
public record ReadBean(HttpRequest<?> httpRequest, #PathVariable #Nullable #Positive Integer id) {
}
ReadController.java:
package com.example;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.annotation.RequestBean;
import javax.validation.Valid;
#Controller("/api/read")
public class ReadController {
#Get
#Produces(MediaType.APPLICATION_JSON)
public HttpResponse<?> read(#Valid #RequestBean ReadBean bean) {
return HttpResponse.ok(bean);
} //read
}
This error appears to be due to my use of #PathVariable. I can get around this error by explicitly declaring the canonical constructor in my record:
package com.example;
import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.PathVariable;
import javax.validation.constraints.Positive;
#Introspected
public record ReadBean(HttpRequest<?> httpRequest, #PathVariable #Nullable #Positive Integer id) {
public ReadBean(HttpRequest<?> httpRequest, Integer id) {
this.httpRequest = httpRequest;
this.id = id;
} //ReadBean
}
Is there any cleaner way to get around this? I recall that annotations on record components are propagated to the associated fields, constructors, etc. It would just be nice to not have to declare the constructor explicitly.
Thanks!

Related

Does findById() actually load data from a JPA repository?

I am a Hibernate beginner. I did a couple of simple tutorials and am trying to write a simple shop backend. Everything works as it should, but I am seeing strange things in my unit tests. When I save an entity, then retrieve it using findById(), it seems that I am simply getting the same object I called save() on, without even retrieving actual values from the database:
package com.bo.learnjava.shop1.repository;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "PRODUCTS")
public class Product {
#Id
#GeneratedValue
#Column(name="ID")
long id;
#Column(name="NAME")
String name = "";
#Column(name="PRICE_CENTS")
int priceCents = 0;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPriceCents() {
return priceCents;
}
public void setPriceCents(int priceCents) {
this.priceCents = priceCents;
}
public long getId() {
return id;
}
}
package com.bo.learnjava.shop1.repository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface ProductRepository extends PagingAndSortingRepository<Product,Long> {
}
package com.bo.learnjava.shop1.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
#DataJpaTest
public class ProductRepositoryTest {
#Autowired
ProductRepository repo;
#Test
void testProductRepository() {
Product p=new Product();
p.setName("Milk");
p.setPriceCents(134);
repo.save(p);
// Modify the value to check that repo.findById() actually retrieves *saved* data
p.setPriceCents(9999);
Optional<Product> productFromRepo=repo.findById(p.getId());**
// I expect productFromRepo to contain the values I called save() with
// (price == 134). But productFromRepo.get() returns exactly the same Java object
// as p (with price == 9999), so no actual data was retrieved from the database - why?
assertTrue(productFromRepo.isPresent());
System.out.println("productFromRepo.priceCents="+productFromRepo.get().getPriceCents()); // Outputs 9999!
assertEquals(134,productFromRepo.get().getPriceCents()); // THIS FAILS!!!
}
}
Why does Hibernate behave like that, and how do I test that stuff I write to the database via Hibernate actually gets retrieved back from the database?
Additionaly to comment about first level cache.
If you are extending JpaRepository you can use
repo.saveAndFlush(p);
or
repo.save(p); repo.flush();
to immediately save data in DB.
After it - repo.findById(p.getId()); will return updated data.

JSON binding deserialization error resolving runtime type

I am trying to make a restful API pick up the right class when provided a JSON object.
Consider the following classes :
package org.acme.validation.model;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonSubTypes;
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME, include=JsonTypeInfo.As.PROPERTY, property="genre")
#JsonSubTypes({#JsonSubTypes.Type(value=Comic.class, name="comic")})
public class Book {
public String genre;
public String getTitle() {
return "lolwut";
}
}
package org.acme.validation.model;
import com.fasterxml.jackson.annotation.JsonTypeName;
#JsonTypeName("comic")
public class Comic extends Book {
public String title;
public String getTitle() {
return this.title;
}
}
After many reads (this is my first Java project) I tried to deserialize using Jackson annotations with abstract class inheritance, with interface implementation, and finally with generic classes, but nothing works. I got as far as coming up with the following code, which I would expect to return foobar when provided with { "genre": "comic", "title": "foobar" } as input :
package org.acme.validation.resource;
import org.acme.validation.model.Book;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Consumes;
import javax.ws.rs.core.MediaType;
#Path("/books")
public class BookResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public <T extends Book> String foobar(T book) {
return book.getTitle();
}
}
However all I get is
javax.ws.rs.ProcessingException: RESTEASY008200: JSON Binding deserialization error: javax.json.bind.JsonbException: Error resolving runtime type for type: T
And when I try the abstract class inheritance I get
avax.ws.rs.ProcessingException: RESTEASY008200: JSON Binding deserialization error: javax.json.bind.JsonbException: Can't create instance
What am I missing ?
I am running the application through Quarkus, FWIW.
I got these errors because Jackson was conflicting with JSON-B : I was trying to use Jackson annotations but the pom.xml was including JSON-B as a dependency. I just had to replace quarkus-resteasy-jsonb by quarkus-resteasy-jackson in the pom.xml and everything (generic classes, abstract class inheritance, interface implementations) worked as expected.

Getting duplicate value output from rest service while running the URI

package Book;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/resrtest")
public class Test {
#GET
#Produces({MediaType.APPLICATION_JSON})
public Student datareturn() {
Student student = new Student();
student.setName("ram");
student.setAge(29);
student.setSection("A");
student.setRoll(11);
student.setFathername("sam");
student.setSchoolname("ANC");
return student;
}
}
Output:
{"Name":"ram","Roll":11,"age":29,"fathername":"sam","name":"ram","roll":11,"schoolname":"ANC","section":"A"}
Why Name and Roll is repeating?
Take a look at repeatable values' case.
{"Name":"ram","Roll":11,"age":29,"fathername":"sam","name":"ram","roll":11,"schoolname":"ANC","section":"A"}
You've probably defined additional getter() methods with provided case sensitive names. If you use Jackson. It automatically scans for getSomething() methods and adds return values to JSON body.
Change your Name and Roll fields to small case (Name -> name, Roll -> roll). Then duplication will be removed.

Spring Boot REST Controller issues

I am having a very strange issue with a Rest Controller. I have a very basic rest controller.
package com.therealdanvega.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import com.therealdanvega.domain.Post;
import com.therealdanvega.service.PostService;
#RestController
public class PostController {
private PostService postService;
#Autowired
public PostController(PostService postService){
this.postService = postService;
}
#RequestMapping("posts/test")
public String test(){
return "test...";
}
#RequestMapping( name="/posts/", method=RequestMethod.GET )
public Iterable<Post> list(){
return postService.list();
}
}
That calls a service
package com.therealdanvega.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.therealdanvega.domain.Post;
import com.therealdanvega.repository.PostRepository;
#Service
public class PostService {
private PostRepository postRepository;
#Autowired
public PostService(PostRepository postRepository){
this.postRepository = postRepository;
}
public Iterable<Post> list(){
return postRepository.findAll();
}
}
That calls a repository to fetch the data.
package com.therealdanvega.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import com.therealdanvega.domain.Post;
#Repository
public interface PostRepository extends CrudRepository<Post, Long> {
Post findFirstByOrderByPostedOnDesc();
List<Post> findAllByOrderByPostedOnDesc();
Post findBySlug(String slug);
}
I am using an H2 in memory database and I only have a single Post record in there and can confirm so by going to the H2 console and running a select again the Post table.
If I visit the /test URL I get exactly what I am expecting which is the string "test..." printed to the browser. If I try and list all of the posts (which again is only 1) the browser starts looping over and over and continue to print out a JSON representing of the 1 post so many times that the application crashes and I see this in the console
2015-11-07 17:58:42.959 ERROR 5546 --- [nio-8080-exec-1]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet dispatcherServlet threw exception
java.lang.IllegalStateException: getOutputStream() has already been
called for this response
This is what my browser looks like when I visit /posts which should only list 1
Post Domain Class
package com.therealdanvega.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.springframework.data.annotation.CreatedDate;
#Entity
public class Post {
#Id #GeneratedValue
private Long id;
private String title;
#Column(columnDefinition = "TEXT")
private String body;
#Column(columnDefinition = "TEXT")
private String teaser;
private String slug;
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
private Date postedOn;
#ManyToOne
private Author author;
#SuppressWarnings("unused")
private Post(){
}
public Post(String title){
this.setTitle(title);
}
// getters & setters
}
Does anyone know what I am doing wrong or missing here? Why isn't it just display the 1 record in JSON format?
It seems that your Post object has a circular reference. The Author object in your Post object has a list of Posts objects and so on. Try putting the #JsonIgnore annotation on the author attribute of your post object.
You can also use the #JsonBackReference and #JsonManagedReference to solve the problem.
From the Jackson documentation :
Object references, identity
#JsonManagedReference, #JsonBackReference: pair of annotations used to
indicate and handle parent/child relationships expressed with pair of
matching properties #JsonIdentityInfo: class/property annotation used
to indicate that Object Identity is to be used when
serializing/deserializing values, such that multiple references to a
single Java Object can be properly deserialized. This can be used to
properly deal with cyclic object graphs and directed-acyclic graphs.
I believe your Posts domain object contains Author domain object, that in turn in it's posts field contains all the posts by that author, which in turn contains author that contains posts... you see where I'm going with this.
It's probably best that you use fetch or load graphs to optimize your query's fetch strategy.

Applying different Jackson filter for different Jersey REST service calls

I am using Jersey to implement JAX-RS REST-style services along with Jackson 2.0.2 for the JSON mapping. One of these REST services returns a List<EntityA> (let's call it indexA) where EntityA contains another List<EntityB> whereas another service just returns a List<EntityB> (let's call it indexB):
#Entity
#JsonAutoDetect
public class EntityA {
#Id
private String id;
#OneToMany
private List<EntityB> b;
...
}
#Entity
#JsonAutoDetect
#JsonFilter("bFilter")
public class EntityB {
#Id
private String id;
private String some;
private String other;
private String attributes;
...
}
#Path("/a")
public class AResource {
#GET
#Path("/")
public List<EntityA> indexA() {
...
}
}
#Path("/b")
public class BResource {
#GET
#Path("/")
public List<EntityB> indexB() {
...
}
}
What I'd like to achieve is to apply a Jackson filter to the indexA invocation so that not all attributes of the child EntityB elements are serialized. OTOH, indexB should return EntityB in its completeness.
I am aware of the existence of a ContextResolver<ObjectMapper>, which I am already using for other purposes. Unfortunately, for the ContextResolver it seems to be impossible to distinguish both service invocations as the Class supplied to ContextResolver.getContext(Class) is ArrayList in both cases (and thanks to type erasure I cannot figure out the generic type parameters).
Are there any hooks better suited at configuring an ObjectMapper/FilterProvider depending on the entity type that is being mapped?
I could use the approach proposed in How to return a partial JSON response using Java?: Manually mapping to a String, but that kills the whole beauty of a declarative annotation-based approach, so I'd like to avoid this.
I was in the same situation, after tons of research, I figured it out, the solution is to use #JsonView and Spring which can inject an ObjectMapper into the JSON Writer without killing the beauty of Jersey.
I am working on a set of REST APIs, I want to get a list of instances of SystemObject and the detail a specific instance of SystemObject, just like you I just want very limited of number of properties of each instance in the list and some additional properties in the detail, I just define Views for them, and add annotation in the SystemObject class. but by default, all properties with no #JsonView annotation will be output to the JSON, but there is a configuration item(SerializationConfig.Feature.DEFAULT_VIEW_INCLUSION) I can use to exclude them.
The problem is that I have to set it to true to meet my need. but I can not change the ObjectMapper which does the magic to convert the object to JSON, by reading the 3 articles below, I got the idea that the only way I can do is to inject a Modified ObjectMapper to Jersey.
Now I got what I want.
It is like you create multiple views against a database table.
These 3 links will help you in different ways:
How to create a ObjectMapperProvider which can be used by Spring to inject
Jersey, Jackson, Spring and JSON
Jersey + Spring integration example
REST resource:
package com.john.rest.resource;
import java.util.ArrayList;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;
import org.codehaus.jackson.map.annotate.JsonView;
import org.springframework.stereotype.Component;
import com.midtronics.esp.common.EspException;
import com.midtronics.esp.common.SystemObject;
import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.accesscontrol.AccessControlBean;
import com.midtronics.esp.model.site.SiteBean;
#Component
#Path("/hierarchy")
public class Hierarchy {
// Allows to insert contextual objects into the class,
// e.g. ServletContext, Request, Response, UriInfo
#Context
UriInfo uriInfo;
#Context
Request request;
// Return the list of sites
#GET
#Path("sites")
#Produces(MediaType.APPLICATION_JSON)
#JsonView({SystemObjectView.ObjectList.class})
public List<SystemObject> listSite(
#HeaderParam("userId") String userId,
#HeaderParam("password") String password) {
ArrayList<SystemObject> sites= new ArrayList<SystemObject>();
try{
if(!AccessControlBean.CheckUser(userId, password)){
throw new WebApplicationException(401);
}
SystemObject.GetSiteListByPage(sites, 2, 3);
return sites;
} catch(EspException e){
throw new WebApplicationException(401);
} catch (Exception e) {
throw new WebApplicationException(500);
}
}
// Return the number of sites
#GET
#Path("sites/total")
#Produces(MediaType.TEXT_PLAIN)
public String getSiteNumber(#HeaderParam("userId") String userId,
#HeaderParam("password") String password) {
try{
return Integer.toString(SiteBean.GetSiteTotal());
} catch(EspException e){
throw new WebApplicationException(401);
} catch (Exception e) {
throw new WebApplicationException(500);
}
}
}
REST model:
package com.john.rest.model;
import java.io.Serializable;
import java.util.ArrayList;
import javax.xml.bind.annotation.XmlRootElement;
import org.codehaus.jackson.annotate.JsonIgnore;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.annotate.JsonView;
import com.midtronics.esp.mobile.model.SystemObjectView;
import com.midtronics.esp.model.common.ICommonDAO;
#XmlRootElement
public class SystemObject implements Serializable
{
private static final long serialVersionUID = 3989499187492868996L;
#JsonProperty("id")
#JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
protected String objectID = "";
#JsonProperty("parentId")
protected String parentID = "";
#JsonProperty("name")
#JsonView({SystemObjectView.ObjectList.class, SystemObjectView.ObjectDetail.class})
protected String objectName = "";
//getters...
//setters...
}
REST model view:
package com.john.rest.model;
public class SystemObjectView {
public static class ObjectList { };
public static class ObjectDetail extends ObjectList { }
}

Categories

Resources