Prevent entities from stealing each other's mapped child entities in JPA - java

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": []
}

Related

Spring Boot API response returns repeating nested JSON

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.

many-to-many mapping with extra column - how to set mapping and how to serialise?

I am very new to Hibernate and I am trying to solve an issue similar to this question, specifically the answer.
I have something very similar set up (in my case it's Author, Book, and the mapping table AuthorBook). I am using this in Spring Data JPA, so I have the following components:
Repositories: AuthorRepository, BookRepository, AuthorBookRepository
Services: AuthorService, BookService, AuthorBookRepository
Controllers: AuthorController, BookController, AuthorBookController
My entities are:
#Entity
public class Author {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "author")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class Book {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "book")
private Set<AuthorBook> authorBooks;
// getters and setters
}
#Entity
public class AuthorBook {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
private Author author;
#ManyToOne
#JoinColumn(name = "book_id")
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}
My understanding is that I should make the following POST requests:
Create an author:
{
"name": "Test Author"
}
Create a book:
{
"name": "Test Book"
}
Create the mapping:
{
"author": {
"id": 1
},
"book": {
"id": 2
},
"isMainAuthor": true
}
First of all: is this the correct way to use this? If no, how should I use it instead? If yes, how is serialisation supposed to work? Because if I do it like this, and then fetch the list of books, the response will be infinitely long like so:
[
{
"id": 2,
"name": "Test Book",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
{
"id": 3,
"author": {
"id": 1,
"name": "Test Author",
"authorBooks": [
...
I know that I could use #JsonIgnore on the Author and Book getters in AuthorBook, and #JsonProperty on the setters so that deserialisation still works. However, as someone who is unexperienced with Hibernate, this seems like a hack to me. What is the cleanest and best way to solve this?
Also, is there a way to update the mapping via the author and book endpoints directly? I might already have an author in the database and want to just add a new book he wrote by adding a new Book entity and providing the relation as part of it, but I seem not to be able to do that.
This is absolutely not a hacker trick. With the #JsonIgnore annotation, this problem is solved. This problem is called infinite recursion with the release of Jackson JSON and nothing can be done with it. For this, they came up with an annotation.
You probably need to set an embedded ID within the mapping table. Keeping the Author and Book table as such, create a new class
#Embeddable
public class AuthorBookId{
#Column(name = "user_id")
private String authorId;
#Column(name = "book_id")
private String bookId;
// Constructors
// Getters and Setters
}
And change the AuthorBook table as
#Entity
public class AuthorBook {
#EmbeddedId private AuthorBookId id = new AuthorBookId();
#ManyToOne
#MapsId("user_id")
#JsonIgnore
private Author author;
#ManyToOne
#MapsId("book_id")
#JsonIgnore
private Book book;
#Column(name = "isMainAuthor")
private boolean isMainAuthor;
// getters and setter
}

How to POST oneToMany relational database entries with Spring Boot

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.

Want to include relationships in Spring REST API

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.

Missing Foreign Keys when mapping Child Entities from JSON / How to generate them?

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);
}

Categories

Resources