EJB 3 persistence with JPA relational mapping - java

I have two entities:
Corpus entity:
#Entity(name = "CORPUS")
public class Corpus implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#OneToMany(mappedBy = "corpus", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Collection<CorpusHistory> corpusHistories;
//Setters and getters...
}
Corpus history entity:
#Entity(name = "CORPUS_HISTORY")
public class CorpusHistory implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="CORPUS_ID")
private Corpus corpus;
//Setters and getters...
}
The corpus entity can have many records of corpus history so I am annotating it with #OneToMany. I want the mapping to be done using the corpus id so I am using #JoinColumn(name="CORPUS_ID") and #ManyToOne annotation in corpus history entity.
Before persisting the corpus object to database I set the corpus history collection to it:
LinkedList<CorpusHistory> corpusHistories = new LinkedList<CorpusHistory>();
for (Change change : changes) {
CorpusHistory corpusHistory = new CorpusHistory();
//corpusHistory.setCorpusId(String.valueOf(corpusId)); ?????
corpusHistory.setRevisionAuthor(change.getName());
corpusHistory.setRevisionDate(change.getWhen());
corpusHistory.setRevisionNote(change.getNote());
//corpusHistoryFacade.create(corpusHistory);
corpusHistories.add(corpusHistory);
}
corpus.setCorpusHistories(corpusHistories);
Records are created all it is ok but in the corpus history table the CORPUS_ID column is always null. And when I am retrieving corpus from database the history list is empty. I do not understand how can I specify the corpus id to corpus history if the corpus record is not created yet?
Isn't this an EJB job to do? With #OneToMany and #ManyToOne mapping the appropriate ID's should be mapped and stored into appropriate columns (in this case the corpus id should be stored in every record of corpus history column CORPUS_ID).
Or I am misunderstanding something here? I have tried many tutorials, no success... I am stuck here.

for (Change change : changes) {
CorpusHistory corpusHistory = new CorpusHistory();
corpusHistory.setCorpus(corpus);
...
}
The owner side is the one without the mappedBy attribute. You must initialize the owner side in order to tell JPA that the association exists. Since you have a cascade on corpus.histories, when you persist the corpus, JPA will also persist the histories.

Related

How to save spring entities solely using the foreign key of an associated object

I have two spring entities, job and employer, which have a bidirectional association.
Job Entity
#Entity
#Table(name = "job")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Job {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "job_id", nullable = false)
private Integer id;
#Column(name = "title", nullable = false)
private String title;
#ManyToOne(optional = false)
#JoinColumn(name = "employer_employer_id", nullable = false)
#JsonBackReference
private Employer employer;
}
Employer Entity
#Entity
#Table(name = "employer")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Employer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "employer_id", nullable = false)
private Integer employerId;
#Column(name = "name", nullable = false)
private String name;
//Mapped by indicates the inverse side of the relationship.
#OneToMany(mappedBy = "employer", orphanRemoval = true)
#JsonManagedReference
private List<Job> jobs = new ArrayList<>();
}
I also have two simple CRUD repositories
Let's say that I have an existing employer object saved in the database. In my jobs service, I want to have a method that creates a new job. My question is, what is the correct Spring boot way to save a new job entry in the database where the employee id foreign key relates back to that existing job in the database.
Here is my first attempt, and it works, however it doesn't seem very efficient. Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save? Is there a way I can avoid making this extra database call, and when we are saving the job to the database, just easily specify an existing employer ID on that new job we are saving to the database? Or is it Spring best practice to have to save the entire employee object here, even if it already exists?
Employer e = employerRepository.findById(0).orElseThrow(IllegalArgumentException::new);
job1.setEmployer(e);
jobRepository.save(job1);
Best way is use getOne so you don't even have to fetch the empoyer
Employer e = employerRepository.getOne(id);
job1.setEmployer(e);
jobRepository.save(job1);
If employer does t exist an exception will be thrown when you save job.
getOne is deprecated in later versions of jpa so use this instead
JpaRepository#getReferenceById(ID)
Good/progressive question:
Why should I have to retrieve the entire employer object from the database, when I really just want to specify the employer ID of the job I am trying to save?
We don't have to! We can:
Employer empler =
entityManager.getReference(Employer.class, 0L);
// handle exception...
Resp. with spring-data:
JPARepository.getReferenceById(...);
"must read" article
jpa-javadoc
spring-data-javadoc
Regarding "how to do it best" (and in which queries it results), i can additionally very recommend:
https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
(Where Employer is analogous to Post and Job analogous to PostComment;)

Spring JPA most efficient way to load ManyToOne relationship?

I have an entity called StoreQuantity, which stores the current in stock quantity of all products/items in a store:
#Data
#Entity
#Table(name = "STORE_QUANTITY")
public class StoreQuantity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_QUANTITY_ID", nullable = false)
private int storeQuantityId;
#ManyToOne
#JoinColumn(name = "PRODUCT_ID", nullable = false)
private Product product;
#Column(name = "INSTORE_QUANTITY")
private int instoreQuantity;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "STORE_ID", nullable = false)
private Store store;
}
Corresponding Store entity:
#Entity
#Table(name = "store")
public class Store implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "STORE_ID", nullable = false)
private int storeId;
#Column(name = "NAME", nullable = false)
private String name;
#JsonIgnore
#OneToMany(mappedBy = "store")
private List<StoreQuantity> storeQuantityList;
}
Im trying to retrieve all the quantities of products in all stores, and export as csv. I currently have thought of two ways of doing so:
Either Retrieve the entire storequantity table in one call, and for each storequantity I print as csv.
public String currentStoreQuantitiesCSV() {
List<StoreQuantity> storeQuantityList = storeQuantityRepository.findAllByOrderByStoreDesc();
for (StoreQuantity storeQuantity : storeQuantityList) {
//StoreId
csvString.append(storeQuantity.getStore().getStoreId()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
Or I call them by store:
public String currentStoreQuantitiesCSV() {
List<Store> storeList = storeRepository.findAll();
for (Store store:storeList){
List<StoreQuantity> storeQuantityList = store.getStoreQuantityList();
for (StoreQuantity storeQuantity : storeQuantityList) {
//Store Name
csvString.append(storeQuantity.getStore().getName()).append(',');
//ProductId
csvString.append(storeQuantity.getProduct().getProductId());
//Product Quantity
csvString.append(storeQuantity.getInstoreQuantity());
csvString.append(',');
}
}
They both work, now it's just a matter of efficiency and ram utilization. I read by default JPA will eagerly load any ManyToOne relationships: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate
So does this mean if I choose option 1, there will be as many copies of store objects for every storequantity object? This will be extremely bad as I only have 20-or so stores, but thousands and thousands of storequantities, and id each of them are loaded with their own store object it will be very bad. Or will every storequantity point to the same store Objects? I'm only considering method two because that way there wouldnt be a lot of store objects in memory.
I did some testing looking at the stack memory, it seems that JPA will automatically map all ManyToOne relationships to one object. So in this case for example we have one store, and 10 storequantities that have a ManyToOne to that store. JPA will only instantiate one store object and point all 10 storequantity objects to that one store, instead of creating one store for every storequantity object. So option 1 will be the most efficient as we decrease the amount of database calls.

JPA merge of a #OneToOne entity relationship

I have a #OneToOne relationship between two JPA entities, the following example is simplified and not the real entity names.
#Entity
public class Stock {
#Id
private Long id;
#OneToOne(fetch = FetchType.Lazy, mappedBy = "stock", cascade = CascadeType.ALL)
private StockDetail detail;
}
And StockDetail
#Entity
public class StockDetail {
#Id
private Long id;
#OneToOne
#JoinColumn(name = "STOCK_ID")
private Stock stock;
}
In my example I need to search for stock by ID and then update the stock detail using merge eg.
Stock stock = em.find(Stock.class, 1L);
StockDetail detail = stock.getDetail();
// Do some updates to detail
detail = em.merge(detail);
At this point with the debugger I can see that the returned detail from the merge is the updated JPA entity. However I have an issue when I do the following again.
Stock stock = em.find(Stock.class, 1L);
StockDetail detail = stock.getDetail();
detail now seems to have the old state of the entity, I'm not sure what's wrong with the above
This was actually a combination of #NicoVanBelle and #janith1024 comments.
There were two entitymanagers, a non-cached em used to do the find and a cached em used for doing the merge.
Once I made the non-cached em do the merge on the stock, it started working.

Hibernate OneToMany and related deletion constraints

I have two related classes:
#Entity
#Table(name = "projects")
public class Project {
#Expose
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
...
#Expose
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER)
private Set<WorkPackage> workPackages;
}
and
#Entity
#Table(name = "work_packages")
public class WorkPackage {
#Expose
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Expose(serialize = false)
#ManyToOne
#JoinColumn(name = "project_id")
private Project project;
}
Now the issue:
i have Project object which contains one or more WorkPackage objects stored in DB.
When i delete the project there is no any violation. Project is deleted, but related WPs are still in DB and referring to not existing (after deletion) project. This isn't behavior i expect. I need a violation when i try to delete project that contains at least one WP.
Apparently I can do it in DB directly but i wonder if there is a way to do it through Hibernate and Annotations?
Thanks!
SOLUTION
The problem was Hibernate created Tables with MyISAM engine, which doesn't allow to generate FK apparently.
So i just changed
property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"
to
property name="hibernate.dialect" value="org.hibernate.dialect.MySQL55Dialect"
and it works ( now Hibernate uses InnoDB engine)
Try this:
...
#Expose
#OneToMany(mappedBy = "project", fetch = FetchType.EAGER,
cascade=CascadeType.ALL,orphanRemoval=true)
With orphanRemoval=true all child records of a parent record in a database will be deleted after the parent gets deleted.
I'm assuming you added the Fk after some test run
The problem lies within the property hibernate.hbm2ddl.auto = update
as it won't modify existing table column definitions.
It will only add a column that doesn't already exist.
It will not modify or delete a column that is already present in db.
If you drop the table in db and try it again (if you are working on a test database obviously) you should find the foreign key correctly created.

JPA repository - improve findAll() performances

I am using JPA repository, I need to retrieve a whole Mysql table(40000 records) wich has 5 foreign keys towards smaller tables (500 records). I need one field of each of these 5 tables.
If I call a JPArepository findall(), it takes a few seconds to retrieve all the data.
I need it to be faster. Is there a way to do that?
I don't know what would be the best solution, if it can be done on mysql side, or must be done on Java side.
All the tables are well mapped to JPA entities :
#Entity
#Table(name = "T_CLIENT")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "code")
private String code;
#OneToOne
private Seller seller;
#OneToOne
private Language language;
#OneToOne
private Address address;
#OneToOne
private Country billCountry;
#OneToOne
private ClientType clientType;
}
Thank you for your answers.
You can choose loading fields from foreign keys tables using EntityGraph mechanism.

Categories

Resources