I am in a situation where I have 100 tables (entity classes) and 100 screens (1 for each).
Let us take a look at 2 screens: ShipviaTopComponent and RouteTopComponent. RouteTopComponent has the following method:
public static void saveRecord() {
Route r = resultList.get(currentPos);
EntityManager entityManager = Persistence.createEntityManagerFactory("EntityLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getRouteKey());
r.setRoute(txtRoute.getText());;
entityManager.persist(r);
entityManager.getTransaction().commit();
}
Now, if I put that method in each of 100 screens, everything will work; however I am looking at extracting it into its own class. That means that it won’t always be Route, but can be Shipvia or any other entity class.
I am expecting my RouteTopComponent to call it with something like the following;
com.demo.viewer.SaveRecord.SaveRecord(r);
That r contains just about everything I will need. Here is the SaveRecord class.
public class SaveRecord {
public static void saveRecord(entity.Route r) {
EntityManager entityManager = Persistence.createEntityManagerFactory("EntityLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getRouteKey());
r.setRoute("01-banana"); //Hardcode this for now
entityManager.persist(r);
entityManager.getTransaction().commit();
}
One of the problems I have – it is expecting entity.Route if I pass ‘r’ to it. But if I pass Shipvia, it will break. What are some ways I can just have it expect something like entity.Anything
Edit: As per suggestion, I am attempting to create a superclass entity.
Superclass AddChg entity – http://pastebin.com/1iW67NVn
Shipvia entity – http://pastebin.com/R53tNHyp (Just original, normal entity)
Route entity – http://pastebin.com/5DCvj340 (Extends AddChg)
The piece of code for SaveRecord (I named it SaveResult) looks like this:
public static <T extends AddChg> void SaveResult(T r) {
EntityManager entityManager = Persistence.createEntityManagerFactory(
"EntityLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
r = entityManager.find(Route.class, r.getId());
r.setSomething("01-banana");
entityManager.persist(r);
entityManager.getTransaction().commit();
}
Now, it seems to work, in a sense that when r. pulls everything form the superclass. There is only one line that seems to be giving me trouble:
r = entityManager.find(Route.class, r.getId());
Route.class has to also vary based on which entity its trying to access. And the r.getId() has to be the PK of the entity that is being saved.
So for example, if user wants to edit RouteTop Screen, the line should read;
r = entityManager.find(Route.class, r.getRouteKey());
But for ShipviaTopScreen it will be;
r = entityManager.find(Shipvia.class, r.getShipvViaCd());
Am I able to pass those as parameters such as:
public static <T extends AddChg> void SaveResult(T r, Class c, String PK)
Edit3: The solution above works, and lastly I have one last problem/question.
SaveResult:
public static <T extends AddChg> void SaveResult(T r, Class c, String pk) {
EntityManager entityManager = Persistence.createEntityManagerFactory(
"EntityLibraryPU").createEntityManager();
entityManager.getTransaction().begin();
r = entityManager.find(c, pk);
r.setSomething("01-banana");
entityManager.persist(r);
entityManager.getTransaction().commit();
}
RouteTopComponent:
public static void saveRecord() {
if (r.getRouteKey() != null) {
r.setRoute(txtRoute.getText());
}
SaveResult.SaveResult(r, Route.class, r.getRouteKey());
}
Problem is with the r.setSomething("");
When I type r. my only suggestions are those in the superclass (You will have to refer to the pastebin links for this question).
In RouteTopComponent I am able to do r.setRoute, but not in SaveResult =(
Edit4: I have pursued the approach of saving entity specific data in the TopComponent, while leaving the generic (common) fields to the superclass. Here is what my code looks like now:
RouteTopComponent:
public static void saveRecord() {
WWEntityManager.entityManager.getTransaction().begin();
r = WWEntityManager.entityManager.find(Route.class, r.getRouteKey());
r.setRoute(txtRoute.getText());
WWEntityManager.entityManager.persist(r);
WWEntityManager.entityManager.getTransaction().commit();
SaveResult.SaveResult(r, Route.class, r.getRouteKey());
}
As you can see, I am now committing a Route.class specific fields first. THEN I call out to the SaveResult method which then saves the generic stuff;
SaveResult
public class SaveResult {
public static <T extends AddChg> void SaveResult(T r, Class c, String pk) {
WWEntityManager.entityManager.getTransaction().begin();
r = (T) WWEntityManager.entityManager.find(c, pk);
r.setChgDate("2012-05-31");
WWEntityManager.entityManager.persist(r);
WWEntityManager.entityManager.getTransaction().commit();
}
}
I am wondering if that is indeed the way I should handle saving a single record (with 2 commits)? It works, but if there is a better practice on how to handle it, that would be pretty awesome!
I’d go for a superclass (let’s call it PersistentObject), from which all the entities inherit the basic fields, like an Id for example.
Then, the parameter of your method would be a PersistentObject instead of the parcitular entity Route. You can also define the parameter as any class that extends PersistentObject if you like:
This site explains the basics in a nice way.
EDIT: Regarding your “EntityManager.find()” issue:
Your option seems ok to me, as the EntityManager.find() method returns an entity by primary key checking if either the first argument is, indeed, a class and checking if the second argument matches with the type of the primary key of the entity. I think you can save yourself some trouble by doing directly r.getClass() instead of having a Class c argument. Still, doing everything in the SaveResult method will require that you provide the PK.
As a suggestion (discard it if it’s either tedious or simply impossible), you can extract that find() logic to another method and pass the entity you get as a parameter to the save method, making it independent of object-specific attributes.
There’s another option: NamedQueries. You can define a “findById” query on each entity and then you call the entity.createQuery Method which returns the object you’re searching. This can be a “NO!” in your particulas case, since you have 100 entities and hence you have to do 100 particular queries (Here, Copy-Paste is a siren calling to you to cause disaster…).
I’ve never done this myself, but maybe you can create an unique “findById” NamedQuery in the mapped superclass and pass the corresponding Id, but you’re up to some particular cast handling.
EDIT 2: Indeed, since the T type extends AddChg the only methods you know you’ll be able to access are the ones on the superclass. With this in mind (and avoiding an overkill) you can define a “setRoute()” in the superclass and override it in your particular classes, giving it the particular behaviour you want.
If you need a more complex approach involving many instance-specific attributes you’ll need to prepare your object before saving it. Your save method should only have the persistence-related operations required to store the object. That “setSomething()” seems to be related to object data itself, so it’s more appropiate to do it in RouteTopComponent as you’re doing it.
EDIT 3: Just use the last persist. The first one persists half of the info and the last one persists everything.