Relation between Entity and Object from service - java

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

Related

Spring Boot, filtered OneToMany(mappedBy)

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.

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.

Is there a way to delete a child entity in a #OneToMany relationship (with a JoinTable) without having to fetch the full collection?

I've got the following classes/relationship (getters & setters not displayed, but present):
public class Contract implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
private String number;
private String volume;
#OneToMany(cascade=CascadeType.REMOVE)
#JoinTable(joinColumns = #JoinColumn(name = "contract_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "attachment_id", referencedColumnName = "id"))
private List<Attachment> attachments;
}
public class Attachment implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Version
#Column(name = "version")
private Integer version;
#Lob
#Basic(fetch=FetchType.LAZY)
#Column(length=2147483647)
private byte[] contents;
private String name;
}
As per my needs/design, I am using a join table.
If I want to delete an attachment from the Contract, I need to load the Contract, and then loop through all the attachments until I find the one I want to remove and remove it from the list.
Although this is functional, it will require a lot of DB communication. If the list of attachments is long, and contains large contents, it will also require large bandwidth.
Is there any other way I can remove it? If I try to remove the attachment directly (ex: Attachment.findById().delete()), it will fail due to the FK relationship - won't it? (I haven't tried this yet, but I suspect it).
Additionally, if I have a very large list of attachments, iterating through them one by one until I find the correct one is not very efficient either.
Does JPA provide any other/better solution?
There's one workaround solution I know - you can create an entity class for join table.
You will have to give a name to your join table within #JoinTable annotation (name attribute), lets say ContractAttachment. Then you can create entity:
#Entity(name = "ContractAttachment") // note the same name of table
#IdClass(ContractAttachmentId.class)
public class ContractAttachment implements Serializable {
static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name="contract_id") // same mappings for columns
private Contract contract;
#Id
#ManyToOne(cascade = CascadeType.REMOVE)
#JoinColumn(name="attachment_id") // same mappings for columns
private Attachment attachment;
// you will also have to override equals and hashcode methods here
}
The class ContractAttachmentId should look like:
public class ContractAttachmentId implements Serializable {
private long contract; // note the same fields names
private long attachment;
// this class should also implement hashcode and equals
}
Now you can remove a single entry in join table and even cause attachment object to be deleted too.
ContractAttachment ca = em.createQuery("select ca from ContractAttachment ca " +
"where ca.contract = :contract and ca.attachment = :attachment")
.setParameter("contract", selectedContract)
.setParameter("attachement", selectedAttachment)
.getSingleResult();
em.remove(ca);

Explicitly changing the name of a column with Hibernate

How can you rename a field name in a table which generates Hibernate?
When you create:
#ManyToMany(targetEntity = GroupRightEntity.class)
or
#ManyToMany(targetEntity = UserRightEntity.class)
Now made with an explicit change of names of columns in the Database.
alter table security_mapping_user rename column sec_mapping_id to secmappingentity_id;
Thank you for your help and understanding.
Hibernate generates links for additional tables. Accordingly, I understand that the generated field names. I need to use annotations, or something else, these column names change. I hope I wrote everything correctly.
We have.
#Entity
#Table(name = "ROLE")
public class RoleEntity implements Role, Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "SEQ_ROLE")
#SequenceGenerator(name = "SEQ_ROLE", sequenceName="SEQ_ROLE", allocationSize = 1)
private Long id;
private String name;
private String description;
#Entity
#Table(name = "URL")
public class UrlEntity implements Url, Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(generator = "SEQ_URL")
#SequenceGenerator(name="SEQ_URL", sequenceName="SEQ_URL", allocationSize = 1)
private Long id;
private String url;
#OneToMany(targetEntity = RoleEntity.class, fetch = FetchType.EAGER, mappedBy="url_id")
private Set<Role> roles;
Later we get a third table. "URL_ROLE"
In this table, the fields are named.
"URL_ID" and "ROLE_ID"
"URL_ID" need to rename the field in the "urlentity_id".
It seems now I built everything correctly.
When defining a ManyToMany you should also define which column your relationship maps on. That's where you should change the name.

Categories

Resources