JPA Entity relationship? - java

I have entity called Shop and different types of shops (sport, clothes, tech...). That list of types will probably be predefined. One shop can have multiple types. What is the best way to represent that?
I created two entities Shop and Type.
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
...
}
#Entity
#Table(name = "type")
public class Type {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
private String name; //sport, clothes, tech...
}
What type of relationship between these two entities should I use?

Given that you said Type is probably predefined, it seems more reasonable to model it as enum, and making use of ElementCollection
(I have to admit that I haven't tried to use them both in combination, I believe it should work though :P )
public enum Type {
SPORT, CLOTHES, TECH
}
public class Shop {
#Id
private Long id;
#ElementCollection
#CollectionTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID")
)
#Column(name="TYPE")
// mapping for enum by usertype or other way, depending on JPA version you are using
private List<Type> types;
}
Of course, you can model SHOP_TYPE as an entity (e.g. ShopType) if you want more complicated operations on it, but what described above looks to me a more reasonable domain model.
Even you do not want the Type to be predefined (i.e. you can create whatever type in your application), it is still more reasonable to model it as a ManyToMany relationship:
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id
#Column(name="TYPE_NAME")
private String name;
}
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#ManyToMany
#JoinTable(
name="SHOP_TYPE",
joinColumns=#JoinColumn(name="SHOP_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID"))
private List<Type> types;
}
Just one thing to note: It does not look right to have a Type entity which contains a String as type name, and refer to Shop (as some of the answer suggested). Type should be an entity of itself, and, for example, different shops having CLOTHES type should refer to same CLOTHES entity (unless you view types as some kind of arbitrary tag)

The Store and Type many to many relationship is linked with a third / join table named STORE_TYPE_MAPS.
Store Entity:
#Entity
#Table(name = "store")
public class Store {
#GeneratedValue(strategy = GenerationType.AUTO)
#Id
private Long id;
#ManyToMany(fetch = FetchType.LAZY, targetEntity=Type.class)
#JoinTable(name="STORE_TYPE_MAPS",
joinColumns=#JoinColumn(name="STORE_ID"),
inverseJoinColumns=#JoinColumn(name="TYPE_ID")
private Set<Type> types;
//... getter-setter
}

If Type is an Entity then make it ManyToMany
#ManyToMany
#JoinTable(name="Store_Type")
private List<Type> types;
also it can be an enum

I rather prefer to create a new entity called ShopType, the ManyToMany relationship will be created as explained below.
This new Entity allows you to have extra columns in the join table, "ShopType", (which can't be done with a simple #ManyToMany). For example, you can add this information: "the number of articles of each type in each shop".
The code is as follows:
public class Shop {
#Id
#Column(name="SHOP_ID")
private Long id;
#OneToMany(mappedBy = "shop", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
public class ShopType {
#ManyToOne
#JoinColumn(name = "SHOP_ID")
private Shop shop;
#ManyToOne
#JoinColumn(name = "TYPE_ID")
private Type type;
private int numberArticle;
}
public class Type {
#Id
#Column(name="TYPE_ID")
private Long id;
#OneToMany(mappedBy = "type", cascade = CascadeType.ALL)
private List<JoinAchatType> joinShopType = new ArrayList();
}
For more information check these links:
Mapping many-to-many association table with extra column(s)
.
The best way to use the #ManyToMany annotation with JPA and Hibernate.

Related

Java entity modeling with table that relates to several others

I have a doubt about how the modeling of my entity would be. Come on, I have a table in the database that serves to save documents from my system, this table has the columns id, fk_id (element foreign key), fk_table (entity name) and file_name (stores the name of my file) .
I did a lot of research before posting my question here, but I didn't find anything related to it, what would my entities, user, patient and doctor?
DB:
id
fk_id
fk_table
file_name
1
21
user
test1.jpg
2
32
doctor
test2.pdf
3
61
user
test10.pdf
4
100
patient
test5.jpg
Class:
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String LastName;
// What would a one-to-many relationship look like?
}
public class patient{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
// What would a one-to-many relationship look like?
}
You can use #Where. But be aware that #Where is a Hibernate annotation. It's not in the JPA standard.
For example in the User entity: (I assume that your table is mapped to an entity called Document)
#Where( clause = "fk_table = 'user'")
#JoinColumn(name = "fk_id")
#OneToMany
private List<Document> documents = new ArrayList<>( );
The following is based only on standard JPA annotations. The idea is to create an inheritance hierarchy for the documents table. The base is:
#Entity
#Table(name = "XX_DOCUMENT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "fk_table")
public abstract class BaseDocument {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#Column(name = "file_name")
private String fileName;
}
Here we define that all entities extending this will go to the same table, with the fk_table column to discriminate. The entities extending it are defined as follows:
#Entity
#DiscriminatorValue("doctor")
public class DoctorDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Doctor doctor;
}
#Entity
#DiscriminatorValue("patient")
public class PatientDocument extends BaseDocument {
#ManyToOne
#JoinColumn(name = "fk_id")
private Patient patient;
}
// and so on
The interesting thing is that we are reusing the column fk_id to point to the right table. From a small experiment, Hibernate seems to not have problems with it. I would suggest that you manage the DB creation another way just to be safe.
The Doctor, Patient etc need not have a common base class, e.g.:
#Entity
#Table(name = "XX_DOCTOR")
public class Doctor {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "doctor")
private Collection<DoctorDocument> documents = new ArrayList<>();
// any doctor-specific fields
}
#Entity
#Table(name = "XX_PATIENT")
public class Patient {
#Id
#GeneratedValue(strategy=SEQUENCE)
private Long id;
#OneToMany(mappedBy = "patient")
private Collection<PatientDocument> documents = new ArrayList<>();
// any patient-specific fields
}
// and so on
You can read a (doctor, patient, ...)'s documents from the relevant collection. You can even query BaseDocument instances based on any criteria.
You can even go ahead and do more fabcy stuff with the Java code. E.g. define an interface HasDocuments:
public interface HasDocuments<D extends BaseDocument> {
Collection<D> getDocuments();
}
Doctor, Patient, ..., implements this, so they can all be treated the same way.

How do I cascade persist an #OneToMany relationship with an #EmbeddedId using Spring Data / Hibernate

I've seen a lot of similar questions asked about this but haven't found a solution that fixes the problem I'm seeing, so apologies up front if this is a redundant question. In my situation I have various types of entities and they're each going to have their own tag associations. So I want a generic Tag class that won't have it's own id, but rather an id / composite key made of the id of the entity it's tagging, plus the tag type. To (attempt to) achieve this I made an #Embeddable id class:
#Embeddable
public class TagId implements Serializable {
#Column(columnDefinition = "BINARY(16)")
private UUID parentId;
private String value;
// Getters, setters...
}
That Id is in turn used by a #MappedSuperclass:
#MappedSuperClass
public class Tag {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
... and then when I want to tag a specific entity, for example using a BookTag, the table would have a book_id column as a foreign key to a Book table taking the place of parentId :
#Entity
#Table(name = "book_tag")
#AttributeOverride(name = "parentId", column = #Column(name = "book_id"))
public class BookTag extends Tag {
// other attributes, getters, setters...
}
Then finally, I have a Book entity:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.parentId")
private List<BookTag> tags;
}
When I then try to save a new Book, with a populated BookTag collection, using a Spring Data JPA repository to repo.save(book), my desired behavior is that the Book is saved, then the id is copied to the BookTag objects, and those are saved. Unfortunately, what I'm seeing in the log is that Book is inserted as expected, then the inserts for the Tag objects are run, but book_id is being bound as null for each of the entries.
I've tried a few other approaches:
#JoinColumn instead of mappedBy
#MapsId with a #ManyToOne reference to Book on BookTag
#GeneratedValue on parentId
None worked, but it is possible my syntax was off. Thanks in advance for anyone who knows how to tackle this problem.
To anyone who wants to do something similar, I finally found a solution that meets my criteria.
TagId was modified to this:
#Embeddable
public class TagId<T> implements Serializable {
#ManyToOne
private T taggedEntity;
private String value;
// Getters, setters...
}
...which leads to a slight modification to Tag...
#MappedSuperClass
public class Tag<T> {
#EmbeddedId
private TagId id;
// Other attributes, getters, setters...
}
...and then BookTag...
#Entity
#Table(name = "book_tag")
public class BookTag extends Tag<Book> {
// other attributes, getters, setters...
}
...and finally Book:
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue
#Column(columnDefinition = "Binary(16)")
private UUID id;
// other attributes, getters, setters...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "id.taggedEntity")
private List<BookTag> tags;
}
Now I can add 1...* BookTags to a Book, and in turn I have to set the Book on all the BookTags, but then it's one call to bookRepository.save() and everything cascades down. It would have been nicer to just do it with an id, but a generic is flexible enough. I'll just have it implement an interface so that toString/hashCode/equals can call getId on parent.
The only other drawback is I couldn't get #AttributeOverride to work, so while I'd prefer that my BookTag table have a book_id column, tagged_entity_id will have to suffice.

Java Spring ManyToOne how to load only reference

I have data being persisted in Spring of employees and "personal development plans". Employee is the dominant class, so to speak. It looks like this:
#Entity(name = "employee")
#Table(name = "EMPLOYEE")
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "EMPLOYEE_ID")
private int id;
// etc...
}
Personal Development Plan looks like this:
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#ManyToOne(optional = false)
#JoinColumn(name = "EMPLOYEE_ID")
private Employee employee;
// etc..
}
In the database it is stored as a foreign key reference from PDP -> Employee.
I want to be able to load a PDP as it is in the database, with only employee id, but i always get the whole Employee object with all attributes. How do i do this?
I tried #ManyToOne(fetch = FetchType.Lazy) but this gives me the following error when fetching:
Type definition error: [simple type, class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested
exception is
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No
serializer found for class
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no
properties discovered to create BeanSerializer (to avoid exception,
disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference
chain:
java.util.ArrayList[0]->nl.kars.lms.model.pdp.PersonalDevelopmentPlan["employee"]->nl.kars.lms.model.Employee$HibernateProxy$AAwzPX4I["hibernateLazyInitializer"])
What am i doing wrong?
If you want only the ID, why not map it without the relationship ?
#Entity(name = "pdp")
#Table(name = "PDP")
public class PersonalDevelopmentPlan implements Serializable {
#Id
#GeneratedValue
#Column(name = "PDP_ID")
private int id;
#Column(name = "EMPLOYEE_ID")
private Long employeeId;
// etc..
}
It is actually how an ORM works... mapping table(relational side) to entities(object side). And mapping between entities is not done via ids but by entity references.
So either just persist the id (so remove strong relation) or use a projection query to just get back the employee Id.
You can create getter:
public Long getEmployeeId(){
return this.employee.getId();
}
Or you can change mapping to value private Long employeeId.

How to stop Hibernate from eagerly fetching a relationship when it is mapped using a column (referencedColumnName) different than the primary key?

I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.

Guidance on mapping the following relationship with ebean using jpa annotations

I have the following two entities in the DB ( structure is fixed ) which I am trying to map using JPA Annotations and EBEAN is ORM.
I have the following beans:
class Item {
public Long id;
public String name;
public Consignee intermediate;
public Consignee ultimate;
}
class Consignee {
public Long id;
public String name;
public String address;
public Item item;
}
And their corresponding tables:
Item
----
id
name
Consignee
---------
id:
name
address
item_id
type: [1,2] / 1: intermediate, 2:ultimate
the main entity is ITEM although the relationship is mapped from the consignee side.
How can I mapped this using the Annotations so that the consignees ( ultimate, intermediate ) are loaded when I fetch the Item object from DB ?
Could you please point me to the right direction
The two tables your are trying to map to Ebean are called Entity Models and the relation between Entity Consignee to Item is a One to Many Relationship.
Such relation can be mapped with a #OneToMany annotation on the Consignee side, and with an #ManyToOne on the Item side.
Also the field type of Consignee can be mapped with an Enumeration persisted as integer, and the remaining fields can be mapped via #Column annotation.
A possible implementation of your requirements could be something like:
public enum CONSIGNEE_TYPE {
INTERMEDIATE,
ULTIMATE
}
#Entity
#Table(name = "Consignee")
public class Consignee extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
public String address;
#Enumerated(EnumType.ORDINAL)
public CONSIGNEE_TYPE type;
#Column(name="item_id")
#OneToMany(cascade = CascadeType.ALL, mappedBy = "consignee", fetch = FetchType.EAGER)
public List<Item> item = new ArrayList<Item>();
//TODO: Generate Constructors/Getters/Setters
}
#Entity
#Table(name = "Item")
public class Item extends Model {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
public String name;
#ManyToOne(optional = false, fetch = FetchType.EAGER)
public Consignee consignee = new Consignee();
//TODO: Generate Constructors/Getters/Setters
}
I removed the ambiguation with the ultimate and intermediate consignee type from the class Item since you can store the type of the consignee on the consignee itself.
Hope this helps.
As a very nice resource for the future, I recommend you to read the Unit Tests available on the source code of the Ebean itself. It ain't pretty but it helped me a lot!

Categories

Resources