I am new in Spring.
I have three entity Invoice, Line and Product.
(1) If I call GET /invoices then I want output like,
{
"invoices": [
{
"id": "101",
"date": "2013-02-14"
},
{
"id": "102",
"date": "2013-02-18"
}
]
}
I have done this, I am able to do this but not able to include relations in the get request.
(2) I want to GET call something like this which include relations.
GET /invoices?include=invoice.lines:embed,invoice_line.product:sideload
I want output like
{
"invoices": [
{
"id": "101",
"date": "2013-02-14",
"lines": [
{
"id": "201",
"product_id": "301",
"amount": 100
},
{
"id": "201",
"product_id": "302",
"amount": 100
}
]
},
{
"id": "102",
"date": "2013-02-18",
"lines": [
{
"id": "203",
"product_id": "301",
"amount": 100
}
]
}
],
"products": [
{
"id": "301",
"name": "Ice Cream"
},
{
"id": "302",
"name": "Waffles"
}
]
}
I got stuck in (2) include relationship, I want to achieve something like this
need help.
Thanks in advance.
You can do it by using hibernate annotations.
Your Invoice entity class should look something like this:
#Entity
#Table(name = "invoices") // your table name
public class Invoice implements java.io.Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name = "id", nullable = false)
private Integer id;
#Column(name = "date")
private String date;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "invoices")
private Set<Line> line = new HashSet<Line>(0);
// getter and setter method
And your Line entity should look something like this:
#Entity
#Table(name = "lines")
public class Line implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "id", nullable = false) // Invoice id
private Invoice invoice;
// product_id and amount and getter and setter method
And make sure you have correct relation in your database.
You can take a look here for a fully tutorial.
Hope it help and happy coding.
UPDATE:
Data Transfer Object for Invoice:
public class InvoiceDTO {
private Integer id;
private String date;
// getter and setter methods
}
And then create another service that return InvoiceDTO:
Invoice invEntity = em.find(Invoice.class, id);
InvoiceDTO invDTO = new InvoiceDTO();
invDTO.setId(invEntity.getId());
invDTO.setDate(invEntity.Date());
return invDTO;
Then when you need you can return your Invoice within relation or you can return InvoiceDTO object.
Related
I have a User model and a TodoItem model where the TodoItem model has a primary key to the User model with a user_id #joincolumn. My issue is the response I get from the getUsers API after I add an item. It creates this super long nested JSON where it repeats itself over and over again. I feel like I'm not handling the primary key case properly.
TodoController.java
#RestController
#RequestMapping("/api")
public class TodoController {
#Autowired
private TodoRepository todoRepository;
#PostMapping("/addItem")
public TodoItem addTodoItem(#RequestBody TodoItem todoItem) {
return todoRepository.save(todoItem);
}
User.java
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name")
private String name;
#Column(name = "password")
private String password;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "user")
private List<TodoItem> todos;
public User() {
}
public User(String name, String password, List<TodoItem> todos) {
this.name = name;
this.password = password;
this.todos = todos;
}
// setter and getters
TodoItem.java
#Entity
#Table(name = "todo_item")
public class TodoItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "todo")
private String todo;
#Column(name = "completed")
private boolean completed;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
public TodoItem() {
}
public TodoItem(String todo, boolean completed) {
this.todo = todo;
this.completed = completed;
}
// setters and getters
Add Item Request
{
"todo": "blahblah",
"completed": false,
"user": {
"id": 6
}
}
Add Item Response
{
"id": 26,
"todo": "blahblah",
"completed": false,
"user": {
"id": 6,
"name": null,
"password": null,
"todos": null
}
}
So already I don't like the way the response is given, why is name, pass, and todos null when the user with id 6 exists, also I just passed it a todoitem, so why is todo null. The database populates properly, it's just that the response seems wrong. And then I think it ties into the main problem I have which is here; this is after I add item to a user:
Get Users Response
[
{
"id": 6,
"name": "joe",
"password": "pass",
"todos": [
{
"id": 26,
"todo": "blahblah",
"completed": false,
"user": {
"id": 6,
"name": "joe",
"password": "pass",
"todos": [
{
"id": 26,
"todo": "blahblah",
"completed": false,
"user": {
"id": 6,
"name": "joe",
"password": "pass",
"todos": [
{
"id": 26,
"todo": "blahblah",
"completed": false,
"user": {
"id": 6,
"name": "joe",
"password": "pass",
"todos": [
{
"id": 26,
"todo": "blahblah",
"completed": false,
"user": {
"id": 6,
"name": "joe",
"password": "pass",
"todos": [
{
"id": 26,
"todo": "blahblah",
And it just continues like that for literally thousands of lines. Even though the response is crazy, the database updates properly, but the API calls can take a while due to this issue
In your TodoItem.java, remove the getter for the User property.
Make sure that you only have the setter for user property in your TodoItem.java.
Essentially, when Spring forms the response, it is doing a ".toString()" like method to map the entity in to a JSON object to pass to the front end.
You have a bidirectional association between your entities, so when the mapper goes in to the user it maps all the todos and because those todos all have a relationship with the user it then gets the user...and again...and again and loop of overflow death.
The "best" way and is common is you should make a DTO class which you construct.
UserTodoDTO:
//Lombok Getter/Setter/ToString and All Args Constructor.
#ToString
#Getter
#Setter
#AllArgsConstructor
public class UserTodoDTO {
private long id;
#JsonProperty("name")
private String username;
private List<TodoItem> todoItems;
}
//Pretend this is full of your 'users'.
List<User> usersFromDatabaseAsEntity = new ArrayList<>();
//Return these and the serialisation will not occur.
final List<UserTodoDTO> viewsOfUser = usersFromDatabaseAsEntity
.stream()
.map(entity -> new UserTodoDTO(entity.getId(), entity.getName(), entity.getTodos()))
.collect(Collectors.toList());
Just be aware, if you do log.info(user) it will do the same thing.
The way to avoid this (there are others) is to add a #JsonIgnore to one side of the relationship (like on the #ManyToOne-Users on the Todos) or override the toString() for the Todo.
Changes to TodoItem
//Will not be mapped to JSON so stops the loop of death.
#JsonIgnore
//Lombok can make the toString() for you but need to
// tell it to ignore this field to stop loop of death.
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
//If not using lombok and you are doing TodoItem.toString() somewhere...
//Remove the user from toString().
#Override
public String toString() {
return "TodoItem{" +
"id=" + id +
", todo='" + todo + '\'' +
", completed=" + completed +
'}';
}
The answer is you have mapped each other entity class ,so when you get Todoitem entity and User gets pickedup and again the Todoitem and it goes on.You don't have to map each and every entities.
I have two entities , product and category with a relation of oneToMany .
Each product have one category and a cagetory can have multiple products .
I can show the list of products in the JsonResponse from the category side but
not from the product side .
What I'm I missing ?
-JsonResponse when using Getting a category by categoryName :
URL :http://localhost:8080/api/categories/electornique
{
"id": 1,
"reference": "cat1",
"name": "electornique",
"produits": [
{
"id": 2,
"reference": "tab1",
"designation": "ordinateurupdate",
"price": 600,
"quantite": 1234
},
{
"id": 3,
"reference": "tel1",
"designation": "tel 1 was updated",
"price": 600,
"quantite": 1234
},
{
"id": 4,
"reference": "ord2",
"designation": "ordinateur",
"price": 400,
"quantite": 3
}
]
}
JsonResponse when using Getting a product by productReference :
URL : http://localhost:8080/api/produits/ord2
{
"id": 4,
"reference": "ord2",
"designation": "ordinateur",
"price": 400,
"quantite": 3
}
I want to acheive this :
{
"id": 4,
"reference": "ord2",
"designation": "ordinateur",
"price": 400,
"quantite": 3,
categorie : { id : "" , reference : "", name: "" }
}
-Category entity :
#Entity
#Table(name="categorie",indexes = {#Index (name="index_categorie_reference",columnList="reference",unique= true),
#Index(name="index_categorie_name",columnList="name",unique= true)
})
public class Categorie implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String reference;
#Column(unique= true)
private String name;
#OneToMany(mappedBy= "categorie")
private List<Produit> produits;
// getters and setters...
}
-Product entity :
#Entity
#Table(name="produit",indexes = {#Index (name="index_produit_reference",columnList="reference",unique= true)})
public class Produit implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String reference;
private String designation;
private double price;
private int quantite;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="categorie_id")
#JsonIgnore
private Categorie categorie;
public Produit() {
}
// getters and setters...
}
You cannot see the categorie property because it's annotated with #JsonIgnore.
But if you remove that annotation, you will get an infinite loop when serializing your object...
Here is a good article about the possible solutions/workarounds.
How should I structure a JSON POST, the controller on my backend, and the POJO classes?
This is for a twitter clone, so a user can have multiple tweets etc
POST
{
"tweet": "Sew one button, doesn't make u a tailor",
"user": "Alex"
}
Tweet Controller
public Tweet createTweet(#RequestBody Tweet tweet) {
return tweetRepository.save(tweet);
}
Tweet Class
#Table(name = "tweets")
public class Tweet {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.PERSIST)
private User user;
...
User Class
#Table(name = "custom_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "tweet_id")
private List<Tweet> tweet = new ArrayList<>();
This is the response I'm getting
{
"id": 1,
"user": {
"id": 2,
"name": "Alex",
"tweet": []
},
"tweet": "Sew one button, doesn't make u a tailor"
}
edit:
If I do a GET on my users endpoint, this is what I get (there should be associated tweets included)
[
{
"id": 2,
"name": "Alex",
"tweet": []
}
]
Here:
{
"id": 1,
"user": {
"id": 2,
"name": "Alex",
"tweet": []
},
"tweet": "Sew one button, doesn't make u a tailor"
}
By the looks of it, your Tweet object has a tweet attribute, and your User object has an array that maps every tweet related to that user, that is currently empty on the example.
It looks to me like a problem on your bidirectional mapping between Tweet and User. Consider using the mappedBy property on bidirectional relationships.
I have two entities, Subject and Lesson. Subject has OneToMany relationship with Lesson.
My database already has 2 sets of objects:
{
"id": 1,
"name": "wow",
"lessons": [
{
"id": 2,
"difficulty": "hard"
}
]
}
and
{
"id": 3,
"name": "wow2",
"lessons": [
{
"id": 4,
"difficulty": "hardy"
}
]
}
Entities are defined as follows
#Entity
public class Lesson {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String difficulty;
#ManyToOne(targetEntity = Subject.class)
private Subject subject;
}
#Entity
public class Subject {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(orphanRemoval=false, cascade= CascadeType.ALL)
#JoinColumn(name = "subject_id")
private List<Lesson> lessons;
}
I want to prevent this object to be saved successfully since it is stealing (1, "wow")'s lesson:
{
"id": 3,
"name": "wow2",
"lessons": [
{
"id": 2,
"difficulty": "hard_overrriden"
}
]
}
Manually iterating over all lessons is an obvious way, but I want an easy solution.
EDIT :
My solution fixes the stealing problem but, creates another too
#ManyToOne
#JoinColumn(updatable = false)
private Subject subject;
.
.
.
#OneToMany(cascade= CascadeType.ALL, mappedBy="subject", orphanRemoval=true)
private List<Lesson> lessons;
This fixes the stealing problem but deletes the orphan if I want to disassociate lesson from subject.
On removing, orphanRemoval = true it just stops disassociating lesson from subject.
Disassociation object is as follows:
{
"id": 1,
"name": "wow",
"lessons": []
}
I'm using JPA to to link a User to many Devices which can have many remote controls and these can contain many Commands (Buttons like ON / OFF / Change Channel / Volume etc.)
Thus far all my #OneToMany and #ManyToOne relations are working fine and making #GET call to my rest service gives me the Users and all of the devices and all the remotes etc. corresponding to that User, etc.
{
"devices": [{
"deviceName": "Bedroom",
"id": 2,
"remotes": [{
"commands": [{
"commandName": "KEY_1",
"id": 4
},
{
"commandName": "KEY_3",
"id": 7
}],
"id": 6,
"remoteName": "Samsung TV Remote"
},
{
"commands": [{
"commandName": "KEY_4",
"id": 8
},
{
"commandName": "KEY_2",
"id": 5
}],
"id": 3,
"remoteName": "Samsung TV Remote 2"
}]
}],
"id": 1,
"userName": "Cris"}
What I am trying to do is to create all of these at once when POSTing a JSON similair to this to my RESTful Webservice.(Mapping them straight to an Entity without manually parsing it)
{
"userName": "Cris",
"devices": [{
"deviceName": "Bedroom",
"remotes": [{
"remoteName": "Samsung TV Remote",
"commands": [{
"commandName": "KEY_1"
},
{
"commandName": "KEY_2"
}]
},
{
"remoteName": "Samsung TV Remote 2",
"commands": [{
"commandName": "KEY_3"
},
{
"commandName": "KEY_4"
}]
}]
}]}
It creates them but all of the Child entities (Devices, Remotes, Commands) are not mapped to their parent, so their Foreign Keys are not being filled.
Here are my Entities:
Users
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String userName;
#OneToMany(mappedBy = "user", fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
private List<Devices> devices;
Devices
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String deviceName;
#XmlTransient
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
private Users user;
#OneToMany(mappedBy = "device", fetch = FetchType.LAZY,cascade=CascadeType.PERSIST)
private List<Remotes> remotes;
Remotes
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String remoteName;
#XmlTransient
#ManyToOne( fetch=FetchType.LAZY)
#JoinColumn(name = "DEVICE_ID")
private Devices device;
#OneToMany(mappedBy = "remote", fetch=FetchType.LAZY,cascade=CascadeType.PERSIST)
private List<Commands> commands;
Commands
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String commandName;
#XmlTransient
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "REMOTE_ID")
private Remotes remote;
POST
#POST
#Override
#Consumes({"application/json"})
public void create(Users entity) {
super.create(entity);
}
Is there a way of automatically mapping the child Entities to their parent after POSTing the JSON or will I need to manually map them to each other ?
Cheers
Ok so it took me a while but It's actually a very simple fix.
All I had to do was set the parent in the child entities when persisting the parent in the #POST method.
Here's an example for adding a device with a remote with commands.
I added some try blocks incase any one of these entities didn't actually have a List of Child entities. It might not be the cleanest solution to preventing crashes caused by these certain Exceptions but it's working for what I need it to do.
#POST
#Consumes("application/json")
public void create(Devices device) {
try {
for (Remotes r : device.getRemotes()) {
try {
for (Commands c : r.getCommands()) {
c.setRemote(r);
}
} catch (Exception e) {
System.out.println(r.getRemoteName() + ": commandList -> null");
}
r.setDevice(device);
}
} catch (Exception e) {
System.out.println("remoteList -> null");
}
em.persist(device);
}