I have the follow problem
I have a basic configuration of spring data rest (Nothing fancy, nothing custom).
Using spring-data-rest-webmvc 2.0.0 RELEASE and spring-data-jpa 1.5.0 RELEASE
Class A
#Entity
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id
private String name;
#ManyToMany
private List<B> b;
// getters setters
}
Class B
#Entity
public class B {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id
private String nameb;
#ManyToMany(mappedBy = "b")
private List<A> a;
// getters setters
}
Repository A
#Repository
#RestResource(rel = "a", path = "a")
public interface ARepository extends PagingAndSortingRepository<A, Integer> {
}
Repository B
#Repository
#RestResource(rel = "b", path = "b")
public interface BRepository extends PagingAndSortingRepository<B, Integer> {
}
When I save an entity works fine, but I don't know how to save a relationship
e.g. save an "A" inside a "B" using http
This is the last thing I try from this answer https://stackoverflow.com/a/13031580/651948
POST http://localhost:8080/api/a
{
"name": "Name of A",
"b": {
"rel": "b",
"href": "http://localhost:8080/api/b/1"
}
}
I get an 201 http code but doesn't save the entity.
Did someone tried this already?
Try just using the URL.
POST http://localhost:8080/api/a
Content-Type: application/json
{
"name" : "Name of A",
"b": "http://localhost:8080/api/b/1"
}
or, in your case, it's probably
"b" : ["http://localhost:8080/api/b/1"]
because A.b is a list and hence you submit an array. Did not test this, though.
This should be the valid way since Spring 2.0 (see Spring Data Rest 2.0.0.RELEASE Breaks Code Working Previously With RC1) and it works for me well.
Related
Please be patient with me. tried my best to explain with sample easy code.
Two Entities - Shop and Product.
Relationship - A Shop can have many Product.
I return a Shop object, it keeps printing like this -
{
"shopId": 1,
"shopName": "S1",
"productList": [
{
"productId": 100,
"productName": "MOBILE",
"shop": {
"shopId": 1,
"shopName": "S1",
"productList": [
{
"productId": 100,
"productName": "MOBILE",
"shop": {
Before i start with the actual issue, i did solve the Cyclic issue partially but arrived at a new problem. I stopped it with the help of #JsonIgnore
Basically when i print my parent(Shop) json object i stopped the cyclic response by using #JsonIgnore in child (Product) class field.
#JsonIgnore
private Shop shop
So, now
API 1 =
#GetMapping("/getShopById")
public Shop getShopById(){
return shopRepo.findById(1L).get();
}
GIVES ME OUTPUT - (Which is perfect as i avoid printing Shop back);
{
"shopId": 1,
"shopName": "S1",
"productList": [
{
"productId": 100,
"productName": "MOBILE"
},
{
"productId": 101,
"productName": "EARPHONE"
}
]
}
But now anytime i want to fetch the Shop from a Product object and send the response i get an error, which is because of the #JsonIgnore i guess, which basically is completely stopping the serialization of the field from Product object.
API 2 =
#GetMapping("/getShopFromTheProductId")
public Shop getShopFromProductId() {
Shop s = productRepo.findById(100L).get().getShop();
return s;
}
GIVES ME ERROR -
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.doubt.StackOverFlow.Shop$HibernateProxy$YEW0qvzw["hibernateLazyInitializer"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.12.3.jar:2.12.3]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1276) ~[jackson-databind-2.12.3.jar:2.12.3]
at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:400) ~[jackson-databind-2.12.3.jar:2.12.3]
at com.fasterxml.jackson.databind.ser.impl.UnknownSerializer.failForEmpty
So to summarize how can i ignore printing/getting the Parent back from Child until and unless i require it explicitly ?
PROBABLE SOLUTION 1 - remove the getter for Shop (private Shop getShop()) from Product entity . But this is not a solution for me as i will never be able to track back to the parent when i may need it in business logic.
MY classes -
Controller -
#RestController
public class MainController {
#Autowired
private ShopRepo shopRepo;
#Autowired
private ProductRepo productRepo;
#GetMapping("/getShopById")
public Shop getShopById(){
return shopRepo.findById(1L).get();
}
#GetMapping("/getShopFromTheProductId")
public Shop getShopFromProductId() {
Shop s = productRepo.findById(100L).get().getShop();
return s;
}
}
Shop Entity -
#Entity
#Table(name = "SHOP")
public class Shop {
#Id
#Column(name = "SHOP_ID")
private Long shopId;
#Column(name = "SHOP_NAME")
private String shopName;
#OneToMany(fetch = FetchType.LAZY,targetEntity = Product.class, mappedBy = "shop")
private List<Product> productList;
........
all the getters and setters
Product Entity -
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#Column(name = "PRODUCT_ID")
private Long productId;
#Column(name = "PRODUCT_NAME")
private String productName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SHOP_ID")
#JsonIgnore
private Shop shop;
........
all getters and setters
To avoid the cyclic problem Use #JsonManagedReference, #JsonBackReference as below.
Add #JsonManagedReference on Parent class, shop entity.
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY,targetEntity =
Product.class, mappedBy = "shop")
private List<Product> productList;
Add #JsonBackReference on child class as below.
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SHOP_ID")
#JsonIgnore
private Shop shop;
So i have come to terms with this problem.
Firstly this is wrong sending a domain object directly as a response. Not at all the best practice.
Best practice is to have a RequestShopDTO Object and Similarly and ResponseShopDTO. We should have DTO with getters and setters same as the domain object, in this case Shop.
Rest API should receive RequestShopDTO object.
Use a factory class/ adapter classes to convert the RequestShopDTO to a Shop Domain object and forward it to the Business layer.
Similarly we should convert the Response Shop domain object to ResponseShopDTO object and send it as response.
we should have like BaseRequest class, extended by something like CreateRequest, UpdateRequest, GetRequest etc... where properties common to all get requests are in GetRequest which is then extended by more specific request classes such as RequestShopDTO.
Similarly we can have a abstract Adapter class like this RequestDtoToDomainBaseAdapter that gets extended by something like
ShopDtoToShopDomainAdapter.
reference - inor's answer - Design Pattern to model Request and Response Objects for Webservices
P.S. - DTO - Data Transfer Object
I have a SpringBoot app and am using the CrudRepository to persist objects to the DB. I have an Product entity class which has a many to one relationship with a Vendor entity. I'm passing JSON that includes the details of both Product and the embedded vendor, but I'd ultimately like to pass just the Product details and include the vendorID. Is there some annotation that can resolve this for me?
Here is my code:
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name="VendorID")
private Vendor vendor;
#Column(name="partnumber")
#JsonProperty("PartNumber")
private String vendorPartNumber;
#JsonProperty("Name")
private String name;
#Entity
public class Vendor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JsonProperty("Code")
private String code;
#JsonProperty("Name")
private String name;
....
#OneToMany(mappedBy = "vendor", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnore
private List<Product> products;
And the JSON I'm passing (which works) looks like this:
{
"Vendor": {
"Id":1,
"Code": "BB-1001",
"Name": "Best Buy",
"Address": "100 Best Buy Street",
"City": "Louisville",
"State": "KY",
"Zip": "40207",
"Phone": "502-111-9099",
"Email": "geeksquad#bestbuy.com",
"IsPreApproved": "false"
},
"PartNumber":"TEST01",
"Name":"Test Product 01",
"Price":99.99
}
I'd ultimately like to remove the JSON object reference to vendor and replace with VendorId.
Solution by annotation
You can use #JsonSerialize to provide a custom serialization to achieve what you want.
#Entity
public class Product {
...
#JsonSerialize(using = VendorToIdSerializer.class)
#ManyToOne
#JoinColumn(name="VendorID")
private Vendor vendor;
...
}
public class VendorToIdSerializer extends JsonSerializer<Vendor> {
#Override
public void serialize(Vendor vendor, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
String vendorIdAsString = String.valueOf(vendor.getId());
jsonGenerator.writeObject(vendorIdAsString);
}
}
Solution by DTO
However, as others have pointed out, the cleaner approach would be to create a DTO class which serves the role of the "view" of Product.
public class ProductView {
private final int vendor;
...
public ProductView(Vendor vendor) {
this.vendor = vendor.getId();
...
}
// getters
}
My issue was answered by using the #JsonBackReference and #JsonManagedReference annotations. I'm going to continue to research the DTOs as suggested above though. Thank-you!
I would also suggest to create a DTO that fit your needs.
With JPA you can create DTO instances using the constructor expression:
Here an example from a great article written by Vlad Mihalcea
https://vladmihalcea.com/the-best-way-to-map-a-projection-query-to-a-dto-with-jpa-and-hibernate/
List<PostDTO> postDTOs = entityManager.createQuery(
"select new " +
" com.vladmihalcea.book.hpjp.hibernate.query.dto.projection.jpa.PostDTO(" +
" p.id, " +
" p.title " +
" ) " +
"from Post p " +
"where p.createdOn > :fromTimestamp", PostDTO.class)
.setParameter( "fromTimestamp", Timestamp.from(LocalDateTime.of( 2016, 1, 1, 0, 0, 0 )
.toInstant( ZoneOffset.UTC ) ))
.getResultList();
I have two entitys, A and B. Lets say, that, A has some fields (name, location). B has some fields too + a #ManyToOne relationship to A.
Now if I run my app, I can see the entitys and their valus in ...myDomain/api and specific entitys in ...myDomain/api/A for example. Now if look at ...myDomain/api/B/1, I can see B-s values + under _links a reference to A. How can I get A to already be as a value in B, not a link.
End result should look smth like this:
{
"_embedded" : {
"B" : [ {
"id" : 1,
"someData" : "data",
"otherData" : "other",
"A" : {
"name": "myName",
"location": "myLoc"
} ]
UPDATE
#Entity
#Data //lombok
public class A extends SuperClass{
private String name;
private String location;
}
#Entity
#Data
public class B extends SuperClass {
private String someData;
private String otherData;
#ManyToOne(optional = false, cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
private A a;
}
public class SuperClass implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected long id;
}
Both entitys have a simple repository interface which extend CrudRepository.
UPDATE II
Now if I add #RestResourece( exported = false ) tag after #ManyToOne tag, I get the A entity "exposed" and I can access the data. But now, doing a POST on my B entity, I can't access it anymore because B isn't found by Resource<B> anymore. Why is that so?
Below is my scenario.
I have two entities.
1) Client
2) Project
Relationship among them is One client provides Many Projects. I have created both the entities in Hibernate as follows,
#Entity
#Table(name="client")
public class Client extends BaseEntity<Long> {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long clientId;
......
......
}
#Entity
#Table(name="Project")
public class Project extends BaseEntity<Long>{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long projectId;
.......
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "clientId")
private Client client;
.......
}
My Service Layer looks like,
#RestController
#RequestMapping("/project")
public class ProjectController {
#Autowired
private ProjectService projectService;
#RequestMapping(value = "/add", method = RequestMethod.POST)
public ResponseEntity<String> add(#RequestBody Project project, BindingResult result) throws JsonProcessingException {
........
projectService.saveProject(project);
....
}
}
My JSON while requesting the server is,
{
"name": "Project 1",
"description": "client Project 1",
"startDate": "2012-07-21 12:11:12",
"endDate": "2017-07-21 12:11:12",
"totalPlannedReleases": 20,
"projectKey": "MAQ",
"avatar":"Not Available",
"client": {"clientId":1}
}
My client (Parent entity) is already persisted in database. I have to reference the existing clientId to the new Project which I am inserting.
I am using Jackson as Json library
Is there any way so that Client entity can be mapped/fetched automatically while inserting Project?
What does 'automatically' mean?
If Project is the owning side of the client - project relationship (which it probably should be as per your requirement), you can call project.setClient(entityManager.getReference(Client.class, id)) (or project.setClient(session.load(Client.class, id)) if you are using Hibernate session) to avoid loading the entire state of the Client entity from the database.
I use GlassFish 4.1 (NetBeans), EclipseLink and the default MOXy json binding.
I have an entity with a composite primary key. I need a flat json structure for both input and output. It seems straight-forward, but...
If I do nothing special, I get a flatten json when marshalling, but the unmarshalling does not work (key = null).
If I add the annotation #XmlPath("."), then it is the opposite: the unmarshalling works, but the key fields are duplicated in the json.
Also, MOXy seems the add a type field in the json, which I never asked.
Entity classes:
The PK:
#Embeddable
public class SensorPk implements Serializable {
#Column(name = "sensor_id")
private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
private String address = ADDRESS_DEFAULT;
// + getter/setters
}
The entity:
(the #org.eclipse.persistence.oxm.annotations.XmlPath is commented)
#Entity
#XmlElement
#Table(name = "sensors")
public class Sensor implements Serializable{
#EmbeddedId
// #XmlPath(".")
private SensorPk composedId;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
// + getter/setters
}
The application configuration:
#javax.ws.rs.ApplicationPath("api")
public class ApplicationConfig extends ResourceConfig {
public ApplicationConfig() {
packages("ch.derlin.glf.bbdata");
}
}
I also tried to install jackson (1.X and 2.X), but impossible to make it work on glassfish 4.
The output without any annotation:
XML:
<sensors>
<sensor>
<address>noaddress</address>
<id>24</id>
<name>TEST</name>
</sensor>
</sensors>
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
Nice, but the unmarshalling of the same json fails: id and address are null. And also, what the hell is this type field ?
With annotation:
XML: idem.
JSON:
[
{
"type":"sensor",
"address":"noaddress",
"id":24,
"address":"noaddress",
"id":24,
"name":"TEST MONNEY"
}
]
But the unmarshalling works properly.
Any idea guys ?
Ok, for those with the same problem, I finally made it work be replacing #EmbeddedId with #IdClass.
The class SensorPk is left untouched, but the Sensor class is rewritten like this:
#IdClass(SensorPk.class)
public class Sensor implements Serializable {
#Column(name = "sensor_id")
#Id private Integer id;
#Column(name = "sensor_address")
#NotNull
#Size(min = 1, max = 10)
#Id private String address = ADDRESS_DEFAULT;
#Column(name = "sensor_name")
#Size(min = 1, max = 45)
private String name;
}
Changes are:
the annotation #IdClass is added at the top,
the SensorPk fields are copy pasted with the annotation #Id