Spring Boot, filtered OneToMany(mappedBy) - java

I have these two entities:
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "title")
private String title;
#OneToMany(mappedBy = "book")
private List<UserBook> userBookList;
}
And
public class UserBook {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "user_id")
private long userId;
#ManyToOne
#JoinColumn(name = "book_id", nullable = false)
private Book book;
#Column(name = "page_read")
private int pageRead;
}
What I would like to achieve is to retrieve the list of books from by postgre database and inject into userBookList (for every books) the object from the related UserBook entity given the current user that is sending the request. As of now, every time I get a list of books, userBookList would contain every item inside the UserBook table related to his specific book.
Is there a clean way to do it?

So what you have above is bi-directional mapping. If you take userBookList, it has multiple UserBooks referring to a book. If you just want a clean JSON output without circular referring, then add #JsonIgnore to the one you dont want to be repeated.

Related

Relation between Entity and Object from service

I'm trying to make a relation between my Book entity and a list of languages that I retrieve through a service.
In my database, each book has a: ID, TITLE, CATEGORY_ID (FK), LANG_ID
Book.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
private Language language; // -> The Column associated in the database is Long LANG_ID
}
Category.java:
#Entity
#Table(schema = Constants.SHEMA, name = "Category")
public class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME")
private String name;
}
Language.java:
public class Language implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
}
I understood the relation between Book & Category as both of them are tables in my database. However, Language is something that I get from a service and isn't persisted in my database.
The languages I get are just an ID and a Name for the language.
My question is: In order to link the language ID to my LANG_ID (the ID of the language in my Book table), what annotation (ManyToOne, Entity, ...) should I write for Language? Should I also put it in my persistence.xml ? I tried a couple but it seems like it's not working well.
Thank you very much
I don't think it is good practice to mix persisted data with non-persisted data as it can cause other unexpected problems. Anyway you can try something like this:
#Entity
#Table(schema = Constants.SHEMA, name = "Book")
public class Book implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "TITLE")
private String title;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CATEGORY_ID")
private Category category;
#Column(name = "LANG_ID")
private Integer langId;
#Transient
private Language language;
#PostLoad
public void loadLanguage() {
// get the language data here
}
}
The language field has no database table, so you cannot use any mapping annotation. From the Java EE docs:
public #interface Transient
Specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.
Example:
#Entity
public class Employee {
#Id int id;
#Transient User currentUser;
...
}
The #PostLoad annotation declares a method to be called after the entity is loaded:
public #interface PostLoad
Specifies a callback method for the corresponding lifecycle event. This annotation may be applied to methods of an entity class, a mapped superclass, or a callback listener class.
First of all, did you consider to store language in your database? I mean language are mostly the same, doesn't change too often, you can also store in a properties file and read them at runtime to use them later.
Anyway, I think you should:
first get from external system languages
store in variable / in memory cache ( like a Map<Long,String> where you can store id and name )
read your data from database
for each row you do
read book language id, read the cache, get out data you need
If you can't change model, just use a dto with your entity and the language and you're fine

Java / Hibernate - Added a new Column to table, now I want to populate it with existing data from another Table

So basically I have two tables : NaturalPerson which holds personalNumber column and NaturalPersonReserve where I added a new column personalNumber. Both tables have existing data in it and I want to populate my NaturalPersonReserve's new Column personalNumber from naturalPerson
s table ( I mean the existing data to update from One tables's column to second)
NaturalPerson Entity :
#Entity
#Table(name = "naturalperson")
public class NaturalPerson implements Serializable {
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private int personId;
#Column(unique = true)
private String personalNumber;
#Column
private String serialNumber;
#Column
private String firstname;
#Column
private String lastname;
#Column
private String birthdate;
#Column
private String gender;
NaturalPersonReserve Entity :
#Entity
#Table(name = "natural_person_reserve")
public class NaturalPersonReserve extends SuperModel{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
#JoinColumn(name = "payment_id")
private PaymentParts payment;
// The relationship
#ManyToOne
#JoinColumn(name = "person_id")
private NaturalPerson person;
#ManyToOne
#JoinColumn(name = "company_id")
private Company company;
#Column(name = "amount", columnDefinition = "DECIMAL(10,2) DEFAULT 0.0")
private double amount;
#Enumerated(EnumType.STRING)
#Column(name = "operation_type")
private EReserveType operationType;
// My added column
#Column(unique = true)
private String personalNumber;
Basically you added a new column, and you want to populate it with data from another table. That's not really a java/spring/hibernate issue. That kind of problem will exist regardless of how your application is built.
I can only see two ways:
Write a SQL statement that will update the table based on a select statement. The syntax may change depending on which DB you use. This post has an Oracle example.
Write a program (in your case probably in Java) that will update each register with the correct data. It will have the same effect as the above option, but it will be implemented in your language of choice.

JPA avoid extra read call for a association object while saving the object

I have two JPA entities like below with their respective getters and setters for each field which I have ommitted in the code below.
1.
#Entity
#Table(name = "posts")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(unique = true)
private String title;
}
2.
#Entity
#Table(name = "comments")
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "post_id", nullable = false)
private Post post;
}
While saving a comment object I have to do something like below
Post post = postRepository.findById(postId)
comment.setPost(post);
commentRepository.save(comment);
Question is how to avoid loading a Post object before saving comment. Basically I want to avoid this extra db read call but I don't want to loose the association.
Post post = postRepository.findById(postId);
One option is to use to simply use brand_id as below but I loose the association which I don't want to loose.
private Long post_id;
EntityManager.getReference(...)
That should do the trick.

How can i map to an object that has two fields of the same entity?

In my Spring MVC app i have the following User entity (stripped down for brevity):
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
}
And the following Application entity:
#Entity
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String type;
private String summary;
}
I would like to extend the Application entity to have a submitter (the applicant) and an approver (who the application is assigned to for approval) of type User:
#Entity
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String type;
private String summary;
private User submitter;
private User approver;
}
And the corresponding extended User entity:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String email;
private String password;
private Set<Application> applications;
}
An Application can have one submitter and one approver, but a User can have multiple applications either submitted or waiting for their approval, so i guess it's a #OneToMany relationship from the user's side.
This means the mapping on the Application side would look something like this:
#Entity
public class Application {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String type;
private String summary;
#ManyToOne
#JoinColumn(name = "user_id")
private User submitter;
#ManyToOne
#JoinColumn(name = "user_id")
private User approver;
}
But what mapping do i use on the User side of the relationship?
I suppose it should be something like this:
#OneToMany(mappedBy = {"approver", "submitter"})
private Set<Application> applications;
But referencing multiple columns with mappedBy is not possible as far as i know.
I'm not even sure if my approach is right. Maybe i should have two separate fields in the User entity for the applications?
I'd really appreciate any advice on how to do this.
With your current mapping, you have only one reference from Application to user, the "user_id". This means, that currently submitter and approver is always identical, which I think is not what you want.
On the table to which your Application entity maps, you need two different columns. They need different names and should be mapped similar to this:
#ManyToOne
#JoinColumn(name = "user_id_submitter")
private User submitter;
#ManyToOne
#JoinColumn(name = "user_id_approver")
private User approver;
On your User entity, you can then map two different #OneToMany relationships, e.g. like this:
#OneToMany(mappedBy = "approver")
private Set<Application> applicationsApproved;
#OneToMany(mappedBy = "submitter")
private Set<Application> applicationsSubmitted;
If you need a combined list, feel free to provide a method which combines the two lists:
public Set<Application> getApplications() {
Set<Application> allApplications = new HashSet<>();
allApplications.addAll(applicationsSubmitted);
allApplications.addAll(applicationsApproved);
return allApplications;
}
(please note: the combined List can of course only be used as read-only and cannot be written to and updated to the database, since it is a combination)

What is the simplest way to create nested objects in Ebean?

I need two Ebean model classes called "States" and "Children". A "State" object can contain nested Child objects(List of children).
Here is the basic States class,
#Entity
public class States extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Constraints.Required(message = "stateName cannot be null")
#Column(nullable = false)
private String statename;
#Column(nullable = true)
private String url;
#Column(nullable = true)
private String parent;
private List<Children> childrenList;
}
Here is the basic Children class,
#Entity
public class Children extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String statename;
#Column
private String child;
}
What are the minimal modifications that should be done to these classes to create State objects using Ebean ORM? I went through the post,
Ebean Query by OneToMany Relationship
But there, a lot of changes have been suggested. I just want the minimal modifications.
All I had to do was, doing a small modification to the "States" class,
#Entity
public class States extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Constraints.Required(message = "stateName cannot be null")
#Column(nullable = false)
private String statename;
#Column(nullable = true)
private String url;
#Column(nullable = true)
private String parent;
#OneToMany(cascade = CascadeType.ALL)
private List<Children> childrenList;
}
Only change I have done here is,
#OneToMany(cascade = CascadeType.ALL)
I did not do any changes to the "Children" class. Before starting the play app I set
play.evolutions.enabled = true
in "application.conf" file. Then using the evolution SQL file that was created in "evolution.default" folder, I adjusted the schema of the database. After that "States" objects were created successfully with nested "Children" objects.

Categories

Resources