I am developing a Shopping Application, In my Application i have table tenant and in tenant table i have have column Binary_id which is primary key in binary table in database. Now when i making a get request to tenant table i am getting all the tenant table fields as JSON. But I have #ManyToOne relation from binary table to tenant i.e tenant can have multiple records in binary. So, while making GET call from POSTMAN client instead of getting tenant details, i need to get all the binary records related to that tenant as JSON.
Now i am getting JSON as follows when making a call to http://localhost:8080/sportsmvc/rest/tenant from POSTMAN Client
[
{
"id": 2,
"binaryId": "1002",
"name": "AltisArena"
},
{
"id": 9,
"binaryId": "1001",
"name": "Agon"
}
]
But i need the responce JSON As below:
[
{
"id": 2,
"name": "AltisArena",
"listOfBinary": [
{
"tenant_id": 2,
"location": "location1",
"description": "ABC"
},
{
"tenant_id": 2,
"location": "location2",
"description": "ABCD"
}
]
},
{
"id": 9,
"name": "Agon",
"listOfBinary": [
{
"tenant_id": 9,
"location": "location3",
"description": "desc1"
},
{
"tenant_id": 9,
"location": "location4",
"description": "desc2"
}
]
}
]
Code snippets:
Tenant Entity:
#Entity
#Table(name="tenant", catalog="db_sports" )
// Define named queries here
#NamedQueries ( {
#NamedQuery ( name="TenantEntity.countAll", query="SELECT COUNT(x) FROM TenantEntity x" )
} )
public class TenantEntity implements Serializable {
private static final long serialVersionUID = 1L;
//----------------------------------------------------------------------
// ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
//----------------------------------------------------------------------
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="id", nullable=false)
private Integer id ;
#Column(name="name", nullable=false, length=300)
private String name ;
//----------------------------------------------------------------------
// ENTITY LINKS ( RELATIONSHIP )
//----------------------------------------------------------------------
#ManyToOne
#JoinColumn(name="binary_id", referencedColumnName="id")
private SwaBinaryEntity swaBinary ;
SWA_Binary Entity:
#Entity
#Table(name="SWA_Binary", catalog="db_sports" )
// Define named queries here
#NamedQueries ( {
#NamedQuery ( name="SwaBinaryEntity.countAll", query="SELECT COUNT(x) FROM SwaBinaryEntity x" )
} )
public class SwaBinaryEntity implements Serializable {
private static final long serialVersionUID = 1L;
//----------------------------------------------------------------------
// ENTITY PRIMARY KEY ( BASED ON A SINGLE FIELD )
//----------------------------------------------------------------------
#Id
#Column(name="id", nullable=false, length=100)
private String id ;
#Column(name="file_location", nullable=false, length=400)
private String fileLocation ;
#Column(name="description", nullable=false, length=200)
private String description ;
TenantRestController:
#RequestMapping( value="/tenant",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
#ResponseBody
public List<Tenant> findAll() {
return tenantService.findAll();
}
TenantServiceImpl:
#Override
public List<Tenant> findAll() {
List<TenantEntity> entities = tenantPersistence.loadAll();
List<Tenant> beans = new ArrayList<Tenant>();
for(TenantEntity entity : entities) {
beans.add(tenantServiceMapper.mapTenantEntityToTenant(entity));
}
return beans;
}
TenantServiceMapper:
public Tenant mapTenantEntityToTenant(TenantEntity tenantEntity) {
if(tenantEntity == null) {
return null;
}
//--- Generic mapping
Tenant tenant = map(tenantEntity, Tenant.class);
//--- Link mapping ( link to SwaBinary )
if(tenantEntity.getSwaBinary() != null) {
tenant.setBinaryId(tenantEntity.getSwaBinary().getId());
}
return tenant;
}
Can anyone please help to to solve this issue.
Thanks in Advance.
Maybe I misunderstood model, but it seems like it's a bit wrong. In what you need JSON you have tenant that have multiple binaries, but in JPA model it's vise versa and tenant have 1 binary.
In TenantEntity shouldn't it be like this ?:
#OneToMany
private List<SwaBinaryEntity> swaBinary
Related
for all instances serialized, from the second occurrence onwards, of the same model class, the objects only have a part of the attributes and their respective values that they should. This way the JSON file structure is not homogeneous and uniform as expected by the old part of the application (Frontend).
[
{
"id": 1,
"fornecedor": {
"cnpj": "80000876000177",
"nome": "ATACADÃO DIA-A-DIA"
},
"itens": [
{
"id": 2,
"produto": {
"gtin": "7891991010856",
"nome": "CERVEJA BUDWEISER"
},
"quantidade": 3,
"preco": 3.5,
"total": 10.5
}
]
},
{
"id": 3,
"fornecedor": {
"cnpj": "19600347000101",
"nome": "SUPERMERCADO BELLA-VIA"
},
"itens": [
{
"id": 4,
"produto": {
"gtin": "7891991010857",
"nome": "CERVEJA HEINEKEN"
},
"quantidade": 4,
"preco": 5.4,
"total": 21.6
}
]
},
{
"id": 5,
"fornecedor": "19600347000101",
"itens": [
{
"id": 6,
"produto": "7891991010856",
"quantidade": 4,
"preco": 3.2,
"total": 12.8
}
]
},
{
"id": 7,
"fornecedor": "80000876000177",
"itens": [
{
"id": 8,
"produto": "7891991010857",
"quantidade": 5,
"preco": 4.9,
"total": 24.5
}
]
}
]
In the JSON above, the instances with id:1 and id:7 have a field named fornecedor that is structurally different and that shouldn't be because the Frontend expects them to be the same. Other samples are id:2 and id:6 with the field produto and so on.
The JSON above is the serialization of the list of model class Pedido below:
#Data
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Pedido {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(View.External.class)
private Long id;
#ManyToOne
#JoinColumn(name = "fornecedor_cnpj")
#JsonView(View.External.class)
private Fornecedor fornecedor;
#OneToMany(mappedBy = "pedido", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonView(View.External.class)
private List<Item> itens;
public List<Item> getItens() {
if(itens == null){
itens = new ArrayList<>();
}
return itens;
}
}
The model class Fornecedor is:
#Data
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "cnpj")
public class Fornecedor {
#Id
#JsonView(View.External.class)
private String cnpj;
#JsonView(View.External.class)
private String nome;
#OneToMany(mappedBy = "fornecedor", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonView(View.Internal.class)
private List<Pedido> pedidos;
#OneToMany(mappedBy = "fornecedor", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonView(View.Internal.class)
private List<Disponibilidade> disponilidades;
}
And the model class Produto is:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "gtin")
#NoArgsConstructor
#Data
public class Produto {
#Id
#JsonView(View.External.class)
private String gtin;
#JsonView(View.External.class)
private String nome;
#OneToMany(mappedBy = "produto", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonView(View.Internal.class)
private List<Disponibilidade> disponibilidades;
public Produto(String gtin) {
this.gtin = gtin;
}
}
Question: The structure of JSON is the representation of expected behavior? If so, could someone tell me how do I make all instances from the first one to always have all attributes with their respective values (as much as that means repeated attributes and values!).
Thanks!
The JSON you are getting is a consequence of the usage of #JsonIdentityInfo that you or someone else may have added to solve an infinite recursion while serializing your Java objects to JSON (you can read more about this at https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion). This means that the only way to have the complete set of attributes in fornecedor and produto is to get rid of #JsonIdentityInfo on those classes. Keep in mind that this might create the infinite recursion problem mentioned above.
#Data
#Entity
public class Fornecedor {
(...)
}
#Entity
#NoArgsConstructor
#Data
public class Produto {
(...)
}
I have my data model that contains 3 tables: User, Profile, UserProfile.
public class User implements Serializable {
private Integer id;
......
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch =
FetchType.LAZY)
#JsonManagedReference
#JsonProperty("profiles")
private List<UserProfile> userProfiles = new ArrayList<UserProfile>();
}
public class Profile implements Serializable {
private Integer id;
......
#OneToMany(mappedBy="profile", cascade = CascadeType.ALL, fetch =
FetchType.LAZY)
#JsonBackReference
private List<UserProfile> userProfiles= new ArrayList<UserProfile>();
}
public class UserProfile implements Serializable {
private Integer id;
#ManyToOne
#JoinColumn(name = "idUser")
#JsonBackReference
private User user;
#ManyToOne
#JoinColumn(name = "idProfile")
#JsonManagedReference
private Profile profile;
}
And here’s my json feed back:
{
"id": 1,
.......
"profiles": [
{
"profile": {
"id": 1,
.....
},
{
"id": 2,
.....
}
}
]
}
I have two questions:
Is it possible to remove the profile attribute and have:
{
"id": 1,
.......
"profiles": [
{
"id": 1,
.....
},
{
": 2,
.....
}
]
}
In a manytomany relationship with an intermediate table that contains a primary key (id), 2 foreign key that are the ids of the 2 tables that have the manytomany relationship, is that how to do it?
For the 1st question, to hide profile attribute, there are 2 options:
1. If you don't need it in any json output, you can add a #JsonIgnore annotation to it;
2. If you need it elsewhere but don't want it here, you can use Projection. Check https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections and https://www.baeldung.com/spring-data-rest-projections-excerpts for reference on how to use projections.
Checked your code again. Your code has some problem.
You only need 2 entities: User and Profile. And just add #ManyToMany relationship to them.
Refer here for a complete sample on ManyToMany https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
I'm having troubles getting into Spring Data
I got entity Product which has Category (I'm guessing relation type is right? Product has one Category, Category has many products)
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id")
private Category category;
}
#Entity
class Category implements Serializable {
public Category() {
}
public Category(String name){
this.name = name;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "category_id")
private Long id;
#Column(unique = true)
private String name;
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Product> products;
}
Now I try to add new Product via Postman, calling my RestController
#PostMapping("/add")
public Product addProduct(#Valid #RequestBody Product product){
return repository.save(product);
}
With 2 following requests
{
"name" : "pork",
"category" : "meat"
}
{
"name" : "chicken",
"category" : "meat"
}
In the result I got 2 following responses
{
"id": 1,
"name": "pork",
"category": {
"id": 1,
"name": "meat",
"products": null
}
}
{
"id": 2,
"name": "chicken",
"category": {
"id": 2,
"name": "meat",
"products": null
}
}
And on database I actually got 2 categories named "meat" (even tho it should be unique. What's more, do I actually need Set<Product> in my Category class? TBH, Category has no intrest in that at all.
There are a few problems with your code.
You are directly using entity as the rest API model. Suggest to create a separate ProductModel with only fields that client has access to.
You mixing category creation together inside product creation, but your category in the request only contains name. To the backend, unless you check whether such a category exists, it's always treated as a new category.
Before you call repository.save, you need let category knows what's the product inside. In your current code, only product know its category.
You don't need Set products in your Category class (and it's recommended to use only #ManyToOne).
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?
In typical situation with embedded resources:
#Entity #Data
class Item {
id, name
...
#ManyToOne
#JoinColumn(name="status", referencedColumnName="ID")
private Status status;
}
#Entity #Data
class Status {
id, name
...
#JsonIgnore //break infinite reference loop during serialization
#OneToMany(mappedBy="status")
private List<Item> items;
}
Instead of having links to Status id's in Item JSON, I want to INCLUDE Status object in Item JSON
{
"itemName": "abc",
... ,
"status": {
"statusName":"ACTIVE",
...
}
"_links": {
...
}
}
I managed embedding doing any of the following:
Marking Item class status property as #RestResource(exported=false)
#Entity #Data
class Item {
...
#RestResource(exported=false) // <-- HERE
#ManyToOne
#JoinColumn(name="status", referencedColumnName="ID")
private Status status;
Marking Status repo interface #RepositoryRestResource(..., exported=false)
#RepositoryRestResource(collectionResourceRel="statuses", path="status", exported=false)
public interface StatusRepository extends JpaRepository<Status, String>
Deleteting repository for Status entity
// DELETED
#RepositoryRestResource
public interface StatusRepository extends JpaRepository<Status, String>{}
QUESTION:
Any of that embeds Status into Item JSON like I want, but I do not have an access to Status Repository anymore to get a Status object by it's ID or do any CRUD on it.
How to embed status in parent Item JSON and still CRUD status via url?
I implemented it as follows:
#Entity #Data
class Item {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
String id;
String name
#RestResource(exported = false) // <---- ADDED
#ManyToOne
#JoinColumn(name="status", referencedColumnName="ID")
private Status status;
}
#Entity #Data
class Status {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
String id
String name
// <-------- DELETED REFERENCE TO PARENT OBJECT
//#OneToMany(mappedBy="status")
//private List<Item> items;
}
Repositories for both Entities exist so it is possible to do CRUD on Status entity as well
#RepositoryRestResource(collectionResourceRel="items", path="items")
public interface StatusRepository extends JpaRepository<Item, String>
{}
#RepositoryRestResource(collectionResourceRel="statuses", path="statuses")
public interface StatusRepository extends JpaRepository<Status, String>
{}
For future generations I'll leave it here.
For convenience:
1) Exposed ids for entities in JSON
/**
* Exposing Ids as properties for entities specified
*/
#Configuration
public class ExposeIdConfig extends RepositoryRestMvcConfiguration {
#Override
protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.exposeIdsFor(Item.class, Status.class);
}
}
So getting list of items looks like:
{
"_links": {
"self": {
"href": "http://localhost:8080/app/items{?page,size,sort}",
"templated": true
}
},
"_embedded": {
"as": [
{
"id": "29117425-f011-4ff9-8952-38b05d3df7f0",
"name": "item 1",
"status": {
"id": "e9192ae7-29f8-4d5e-ad62-cad8d87de9e2",
"name": "ACTIVE"
},
"_links": {
"self": {
"href": "http://localhost:8080/app/items/29117425-f011-4ff9-8952-38b05d3df7f0"
}
},
...
}