JPA cascade update error. Am I doing this the wrong way? - java

I'm using JPA on a SWING application in JAVA that connects to an Apache DERBY embedded database. I use Netbeans as my IDE and use many of the "supposedly" helpful templates. My problem is simple, but it's difficult for me to explain so I will paste the relevant code here and try to explain at the bottom.
#Entity
public class AnioLectivo implements Serializable, Comparable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#OneToMany(mappedBy = "anioLectivo", cascade=CascadeType.ALL)
private List<Compensatorio> compensatorios;
...
}
#Entity
public class Compensatorio implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#ManyToOne
private AnioLectivo anioLectivo;
...
}
These two are the entities that i want to persist.
public class AnioLectivoJpaController
{
public void edit(AnioLectivo anioLectivo) throws NonexistentEntityException,
Exception
{
EntityManager em = null;
try {
em = getEntityManager();
em.getTransaction().begin();
AnioLectivo persistentAnioLectivo = em.find(AnioLectivo.class,
anioLectivo.getId());
...
List<Compensatorio> compensatoriosOld =
persistentAnioLectivo.getCompensatorios();
List<Compensatorio> compensatoriosNew = anioLectivo.getCompensatorios();
...
List<Compensatorio> attachedCompensatoriosNew = new ArrayList<Compensatorio>();
for (Compensatorio compensatoriosNewCompensatorioToAttach : compensatoriosNew) {
compensatoriosNewCompensatorioToAttach =
em.getReference(compensatoriosNewCompensatorioToAttach.getClass(),
compensatoriosNewCompensatorioToAttach.getId());
attachedCompensatoriosNew.add(compensatoriosNewCompensatorioToAttach);
}
compensatoriosNew = attachedCompensatoriosNew;
anioLectivo.setCompensatorios(compensatoriosNew);
...
}
This is a class that netbeans generates using the annotations of the entity AnioLectivo that i pasted before. As you can see, i only pasted the code relevant to the problem to keep it simple because i know thanks to the debug tool of netbeans that the problem is here.
Now I'll try to explain exactly what happens.
I create instances of AnioLectivo in one part of the program and persist them ok. Then in another part i must create and add instances of Compensatorio to the Compensatorio's List in an instance of AnioLectivo. Now I want to save this modification, which I assume is made using the edit method in the class AnioLectivoJpaController and I found this error:
java.lang.IllegalArgumentException: An instance of a null PK has been incorrectly provided for this find operation.
at oracle.toplink.essentials.internal.ejb.cmp3.base.EntityManagerImpl.findInternal(EntityManagerImpl.java:309)
at oracle.toplink.essentials.internal.ejb.cmp3.EntityManagerImpl.getReference(EntityManagerImpl.java:176)
at org.sigeb.local.service.dao.jpa.AnioLectivoJpaController.edit(AnioLectivoJpaController.java:113)
at org.sigeb.local.views.datosIniciales.AdministrarCursosPopUp.guardarCambios(AdministrarCursosPopUp.java:574)
at org.sigeb.local.views.datosIniciales.AdministrarCursosPopUp.jBGuardarCambiosActionPerformed(AdministrarCursosPopUp.java:394)
at org.sigeb.local.views.datosIniciales.AdministrarCursosPopUp.access$1000(AdministrarCursosPopUp.java:44)
at org.sigeb.local.views.datosIniciales.AdministrarCursosPopUp$11.actionPerformed(AdministrarCursosPopUp.java:204)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1995)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2318)
...
the problem, as I see it, occurs in this line of code in the edit method of AnioLectivoJpaController:
em.getReference(compensatoriosNewCompensatorioToAttach.getClass(),
compensatoriosNewCompensatorioToAttach.getId());
Why? Well if you see the entities, I have defined that the id of all entities are to be generated by the persistence unit, but this only happens when the entity itself is told to persist. As I create the Compensatorio's instances I never set the id explicitly and when it arrives to that line I quoted up there, compensatoriosNewCompensatorioToAttach.getId() returns null.
It's my understanding that ORM's like JPA have Persistence by Reachability, that allows that if an object A is related to an object B, persisting A also persists B. But in this case it seems like it's implemented in a very inconvenient way(at least for me), because it forces me to persist every object of my collection explicitly when it would be more usefull to persist the object that owns that collection and then the objects in that collection be persisted automatically
Is there something I'm doing wrong?, maybe I should face this problem from another angle, but I don't know how, or if any, what angle?. Why does the people of netbeans make that template that way, why is it useful to execute that method to try to search the objects in the DB and bring it to the persistence context, do i need to persist every object myself? if that's so why do they claim to have Persistence by Reachability if the persistence can only be made in one direction only.
I'm clearly wrong in this, what I'm seeking it's a coherent explanation of how would have to be explicited the relationship between those entities(if i actually did a mistake in the way i created them, because in every book and tutorial i read it's done like that) to make it work so i don't need to persist every object of that collection, or, if i need to drop that template from netbeans and make the code for all the CRUD operations myself, i will like to hear advice on how is convenient to proceed in this case.

It seems to be you can call em.merge(anioLectivo) insted of all this code.

Related

Dealing with EntityNotFoundException

I am creating simple REST API. I want to create an object via post method. Object contains other object. When I want to post it, I am receiving EntityNotFoundException which is thrown when nested object does not exist.
Code of object that I want to post:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Book {
private String title;
#ManyToOne
private Author author;
#Id
#Column(unique = true)
private String isbn;
}
Service of this object:
#Service
#RequiredArgsConstructor
public class BookServiceImpl implements BookService {
private final BookRepository bookRepository;
private final AuthorRepository authorRepository;
#Override
public Book save(Book book) {
try {
Author byId = authorRepository.getById(book.getAuthor().getId());
} catch (EntityNotFoundException e) {
authorRepository.save(book.getAuthor());
}
return bookRepository.save(book);
}
}
After using post method I get this error:
javax.persistence.EntityNotFoundException: Unable to find com.jakubkolacz.qualificationTask.domain.dao.Author with id 0
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:163) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at org.hibernate.event.internal.DefaultLoadEventListener.load(DefaultLoadEventListener.java:216) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
I thought that using try catch to check if object exist, and saving author if necessary will help but it did not.
My question is where should I add some code to solve the problem. I understand why it is happening but do not know how to resolve it. The necessary thing is that I can not create service to add authors manually, they have to be added to repo only during adding new book.
The problem is that the save operation is not being cascaded down to the author object. You should add a cascade type inside ManyToOne annotation:
#ManyToOne(cascade = CascadeType.ALL)
Exception handling in Spring
If you are specifically wondering how to handle exceptions in Spring, then I would highly recommend THIS tutorial.
Entity Creation
First I would like to point out two minor problems with your entity creation.
1)#ManyToOne : while it is not necessary, I always like to annotate a many-to-one relationship with the #JoinColumn annotation. It just acts as a simple and friendly visual reminder that (assuming your relationship is bidirectional) this side is the owner of the relationship(has the foreign key)
2)#Id : as it currently stands, the JPA provider(lets assume hibernate) assumes that you the developer are taking care of assigning a unique identifier to the id field. Granted, this is sometimes neccessary when dealing with old legacy databases. However, if you are not dealing with a legacy database, I would recommend that you delete #Column(unique = true) and the String value to replace them with:
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long isbn;
#GeneratedValue will allow the system to automatically generate a value for isnb field.
strategy = GenerationType.IDENTITY tells the underlying database to handle the uniqueness and the auto incrementation in a way specific to the relational database.
Long instead of String because it can hold a larger number of bytes.
Service implementation
I have a few things to say about the BookServiceImpl class but first, good job on implementing an interface with BookService most people forget to program to an interface.
1) Single Responsibility
You are using both BookRepository and AuthorRepository which is of course fine(If it works it works). However, moving forward you should be weary not to add too many dependencies to one class. If you do so, you are breaking the Single Responsibility principle, which makes code harder to test and refactor.
2) Try and catch
The code inside your try catch blocks is a little confusing, especially since you have not shown the Author entity class. However, I am assuming you logic goes like this: if the author does not exist, save the author. Lastly save and return the book. Now you are correct in thinking that you handle the exceptions in the catch block. However, there is quite a lot to question here and only so little code to go on.
My overall recommendations
1) Break this method up : This method is trying to do three things at once. Create one method for saving the book, one for looking for the author and one for saving the author. This will allow for greater code reuse moving forward.
2) Read up on CascadeType : Specifically PERSIST, that might help you with your issues. Also, look into a many-to-many relationship as it is not uncommon for multiple books to have multiple authors.

How does JPA cache refreshes entities externally modified?

I faced an issue earlier with JPA.
I have two apps : the main one, using Java/JPA (EclipseLink), and a second one, using PHP. The two apps have access to the same database.
Now, I'm accessing an "Expedition" object through Java, then calling the PHP app through a web-service (which is supposed to modify an attribute of this object in the shared database table "Expedition"), then accessing this attribute through the Java app.
Problem is, the object seems not to be modified in the Java app, even if it is modified in the database. I'm thinking about a cache problem.
The original code (simplified) :
System.out.println(expedition.getInfosexpedition()); // null
// Calling the web-service (modification of the "expedition" object in the database)
this.ec.eXtractor(expedition);
System.out.println(expedition.getInfosexpedition()); // Still null, should not be
Definitions of the "Expedition" and "Infosexpedition" classes :
Expedition :
#Entity
#Table(name = "expedition")
#XmlRootElement
public class Expedition implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idExpedition")
private Integer idExpedition;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "idExpedition")
#XmlTransient
private Infosexpedition infosexpedition;
Infosexpedition :
#Entity
#Table(name = "infosexpedition")
#XmlRootElement
public class Infosexpedition implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idInfoExpedition")
private Integer idInfoExpedition;
#JoinColumn(name = "idExpedition", referencedColumnName = "idExpedition")
#OneToOne(optional = false)
#XmlTransient
private Expedition idExpedition;
I've been able to make the original code work by doing this :
System.out.println(expedition.getInfosexpedition()); // null
// Calling the web-service (modification of the "expedition" object in the database)
this.ec.eXtractor(expedition);
try
{
// Getting explicitly the "infosExpedition" item through a simple named request
Infosexpedition infos = this.ec.getFacade().getEm().createNamedQuery("Infosexpedition.findByIdExpedition", Infosexpedition.class)
.setParameter("idExpedition", expedition)
.setHint("eclipselink.refresh", "true")
.setHint("eclipselink.cache-usage", "DoNotCheckCache")
.setHint("eclipselink.read-only", "true") // This line did the trick
.getSingleResult();
expedition.setInfosexpedition(infos);
}
catch (NoResultException nre) {}
System.out.println(expedition.getInfosexpedition()); // Not null anymore, OK
I'm trying to understand what happens here, and why did I had to specify a "read-only" hint to make this work... Before that, I tried almost everything, from evictAll() calls to detach()/merge() calls, and nothing worked.
Can someone help me to understand how the different levels of cache worked here ? And why is my newly created line "read-only" ?
Thanks a lot.
The settings you are using are attempting to bypass the cache. ("eclipselink.read-only", "true") causes it to bypass the first level cache, while the ("eclipselink.cache-usage", "DoNotCheckCache") makes the query go to the database instead of pulling data from the second level cache. Finally ("eclipselink.refresh", "true") refreshes the data in the shared cache rather then return the prebuilt object. Your facade must be using the same EntityManager for both requests even though you have made changes to the objects between the requests. As mentioned in the comments, an EntityManager is meant to be used as a transaction would, so that you are isolated from changes made during your transactions. If this doesn't work for you, you should clear or release the entityManager after the first call, so that the calls after the web-service modifications can be picked up.
If applications outside this one are going to be making data changes frequently, you might want to look at disabling the shared cache as described here:
https://wiki.eclipse.org/EclipseLink/FAQ/How_to_disable_the_shared_cache%3F
And also implement optimistic locking to prevent either application from overwriting the other with stale data as described here:
https://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Locking/Optimistic_Locking
What you call cache is the 1st level cache, id est the in memory projection of the database state at a time t.
This "cache" has the same lifecycle that the entity manager itself and generally won't be refreshed until you explicitely clear it (using myEntityManager.clear()) (you shouldn't) or force it to refreh a specific entity instance (using myEntityManager.refresh(myEntityInstance), this is the way you should go)
See Struggling to understand EntityManager proper use and Jpa entity lifecycle for a more detailed explanation

In Spring Data is it possible to delete entities using an entity's unique attribute?

Is it possible to delete entities using an entity's unique attribute?
In Spring Data 1.4.3.RELEASE, adding methods to find by unique attributes is very easy but, I haven't found a way to do it with delete.
In the following code, Spring automagically handles the findByAddress, is there something similar for delete?
Something like void deleteByAddress(String hwAddress);, I have added it to TerminalRepository but it doesn't work.
public Terminal {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(unique=true)
private String hwAddress;
...
}
public interface TerminalRepository extends
CrudRepository<Terminal, Long> {
Terminal findByAddress(String hwAddress);
}
Of course it is possible to find the entity by address and then use the delete(Terminal) method passing the found entity as parameter. But this wouldn't be good in terms of performance as it will be making one unnecessary call to the database database, i.e., one avoidable call to find the object and another one to delete it
I don't think there's anything built-in for that. You'd have to use the custom method support to roll your own:
http://docs.spring.io/spring-data/jpa/docs/1.4.3.RELEASE/reference/html/repositories.html#repositories.custom-implementations
I was facing the same issue. But, when I annotated method in repository interface with #Modifying , it started working. But, I don't know how it started working. Can anybody explain?

Passing complex JPA Entities to the controller with POJO

My team is coding an application that involves editing wikipedia-like pages.
It is similar to the problem we have with registration:
A straightforward implementation gives something like
public static void doRegistration(User user) {
//...
}
The user parameter is a JPA Entity. The User model looks something like this:
#Entity
public class User extends Model {
//some other irrelevant fields
#OneToMany(cascade = CascadeType.ALL)
public Collection<Query> queries;
#OneToMany(cascade = CascadeType.ALL)
public Collection<Activity> activities;
//...
I have read here and there that this fails. Now, in Play!, what is the best course of action we can take? There must be some way to put all that data that has to go to the server in one object, that easily can be saved into the database.
EDIT: The reason this fails is because of the validation fail. It somehow says "incorrect value" when validating collection objects. I was wondering if this can be avoided.
SOLUTION: Changing the Collection to List has resolved the issue. This is a bug that will be fixed in play 1.2 :)
Thanks beforehand
this works. To be more clear, you can define a controller method like the one you wrote:
public static void doRegistration(User user) {
//...
}
That's completely fine. Just map it to a POST route and use a #{form} to submit the object, like:
#{form id:'myId', action:#Application.doRegistration()}
#{field user.id}
[...]
#{/form}
This works. You may have problems if you don't add all the field of the entity in the form (if some fields are not editable, either use hidden inputs or a NoBinding annotation as described here).
EDIT: on the OneToMany subject, the relation will be managed by the "Many" side. That side has to keep the id of the related entity as a hidden field (with a value of object.user.id). This will solve all related issues.
It doesn't fail. If you have a running transaction, the whole thing will be persisted. Just note that transactions are usually running within services, not controllers, so you should pass it from the controller to the service.

Java JPA OneToMany - "Many" relationship not refreshing

I'm running into a strange problem using the Toplink implementation of JPA. I'm writing a stand-alone application to manage loans.
I define a LOAN class that has a OneToMany (bi-directional) relationship with a NOTICE class. I created a GUI to display all the notices for a particular loan. In another part of my program I can send out late notices which is saved as a NOTICE. However, when I try to display all the notices for a loan, the late notice is NOT appearing. I've checked the database and an entry has been saved but for some reason the notice is not being pulled.
Only when I restart my application does the notice appear.
Because I've defined a OneToMany relationship, I, myself, am not making a "direct" query into the database - I'm letting JPA handle retrieving all the notices for me.
As a fix, I created a query to simply get all notices for a particular loan. This works. However, I thought by defining the OneToMany relationship between the two classes, this should be handled for me. It seems like something is not being refreshed properly... as if an "older" list of notices is being used instead of refreshing from the database?
How are you 'sending the late notice'? There needs to be a statement similar to:
MyLoanInstance.listOfNotices.add(MyNoticeInstance);
even if you already have a
MyNoticeInstance.setLoan(MyLoanInstance);
statement
Without that call, you will have to either completely reload the instance you are working with, or restart your application.
Eclipselink does not automatically update (until reloading everything) the collection on the M side of a 1:M relationship when you modify the 1 side, nor does it update the reference in the 1 side of the 1:M relationship if you modify the collection in the M side.
As a side note, you should consider checking out EclipseLink, it evolved out of what was TopLink, you should be able to directly swap the EclipseLink .jar with the TopLink .jar, if only to recieve a few depreciation warnings.
Eclipse Link
Have you tried to do a flush on your entity manager after you have persisted the NOTICE ?
[ entityManager.flush() ]
I suppose the following
#Entity
public class Loan {
private Integer id;
private List<Notice> noticeList;
#Id
#GeneratedValue
public Integer getId() {
return this.id;
}
#OneToMany(mappedBy="loan")
public List<Notice> getNoticeList() {
return noticeList;
}
}
#Entity
public class Notice {
private Integer id;
private Loan loan;
#Id
#GeneratedValue
public Integer getId() {
return this.id;
}
#ManyToOne(fetch=FetchType.LAZY)
public Loan getLoan() {
return this.loan;
}
}
You have said
In another part of my program I can send out late notices which is saved as a NOTICE
Maybe you have two EntityManager instances (one for each part of your program) so the last Notice saved is not showed because both entityManager does not share the same persistence context (One is not aware of the changes occured in the other). What you can do is clear your persistence context and then retrieve it again as follows
In anther part of your program (after persisting your Notice) call
entityManager.flush();
And when you want to retrive a Loan and its Notice
entityManager.clear();
Query query = entityManager.creatQuery("from Loan l left join fetch l.noticeList where l.id = :id");
query.setParameter("id", yourLoanId);
Check it out and tell me whether it works fine.
regards,

Categories