2622 words

Updating Entities

#1
2017-08-10 13:35

Hello,

I'm currently writing a program using JavaFX and ObjectDB and I'm encountering a problem with Entities not sending their updates to each other.

I have two Entities in question at the moment: Employee and Paygrade.

@Entity public class Employee
{
 private String id;
 private String fName;
 private String lName;
 private Department dept;
 private Paygrade pay;
 ...
}
 
@Entity public class Paygrade
{
 private int level;
 private int rate;
 ...
 public boolean setRate()
 {
  //Error Checking - All have been verified to be passed
 
  Main.paygradeDB.getTransaction().begin();
  this.rate = rate;
  Main.paygradeDB.getTransaction().commit();
 
  return true;
 }
}

Each of these Entities are managed in Main

public class Main extends Application
{
 private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("$objectdb/db/payrollDB.odb");
 public  static EntityManager employeeDB = emf.createEntityManager();
 public  static EntityManager paygradeDB = emf.createEntityManager();
 ...
}

I use a set of controller classes for each of the models. Each controller package consists of Overview, Add, Modify, Delete.

Overview uses a TabelView to display its content and due to my limited knowledge of Java and ObjectDB I have chosen to not use things such as StringProperty since I didn't know how to make it possible to persist them in ObjectDB. Due to this, I've chosen to write a function called setTable() for each of the Overview controllers which consists of content similar to this for both Employee and Paygrade...

public void setTable()
{
 employees = FXCollections.observableArrayList(Query.employeeTable());
 
 table.setItems(employees);
 
 id.setCellValueFactory(itemData -> new ReadOnlyStringWrapper(itemData.getValue().get[field]()));
 id.setCellFactory(c -> new TableCell<Employee, String>()
 {
  @Override protected void updateItem(String item, boolean empty)
  {
   if(item != null)
   {
    setText(item);
    setAlignment(Pos.CENTER);
   }
  }
 });
 
 lName.setCellValueFactory(itemData -> new ReadOnlyStringWrapper(itemData.getValue().getLastName()));
 lName.setCellFactory(c -> new TableCell<Employee, String>()
 {
  @Override protected void updateItem(String item, boolean empty)
  {
   if(item != null)
   {
    setText(item);
    setAlignment(Pos.CENTER);
   }
  }
 });
 
 fName.setCellValueFactory(itemData -> new ReadOnlyStringWrapper(itemData.getValue().getFirstName()));
 fName.setCellFactory(c -> new TableCell<Employee, String>()
 {
  @Override protected void updateItem(String item, boolean empty)
  {
   if(item != null)
   {
    setText(item);
    setAlignment(Pos.CENTER);
   }
  }
 });
 
 dept.setCellValueFactory(itemData -> new ReadOnlyStringWrapper(itemData.getValue().getDepartment().getName()));
 dept.setCellFactory(c -> new TableCell<Employee, String>()
 {
  @Override protected void updateItem(String item, boolean empty)
  {
   if(item != null)
   {
    setText(item);
    setAlignment(Pos.CENTER);
   }
  }
 });
 
 pay.setCellValueFactory(itemData -> new ReadOnlyObjectWrapper<Double>(itemData.getValue().getPaygrade().getRate()));
 pay.setCellFactory(c -> new TableCell<Employee, Double>()
 {
  @Override public void updateItem(final Double item, boolean empty)
  {
   if(item != null)
   {
    setText("$" + df.format(item));
    setAlignment(Pos.CENTER);
   }
  }
 });
}

The Query function is as follows

public static ArrayList<Employee> employeeTable()
{
  String str = "SELECT FROM Employee ORDER BY lastName, firstName, department.name, paygrade.rate";
  return (ArrayList<Employee>) Main.employeeDB.createQuery(str, Employee.class).getResultList();
}

The problem I'm facing is when I use handleModify() from my PaygradeModify class - the change to the Paygrade is not shown when I go to view my Employee table...

public class PaygradeModify
{
 @FXML private TextField paygrade;
 
 private PaygradesOverview controller;
 private Paygrade p;
 
 ...
 
 @FXML private void handleModify()
 {
  String rate = paygrade.getText();
 
  //Error Checking - all checks get passed 
 
  if(/*No Errors*/)
  {
   p.setRate(rate);
   controller.setTable();
  }
  else
   {Main.showErrorDialog();}
}

However, it is worth noting that the changes ARE made if the EmployeeOverview table has NOT been visibly loaded during Runtime once. As soon as the table is loaded: all content is displayed accurately. However, once a change has been made to paygradeDB after this visual load - the Employee table refuses to update the displayed Paygrades.

Initially I thought it was a problem in my code for how I loaded the content into the table, but then I tried checking the content from my Query.employeeTable() function. Interestingly, this function was returning the unmodified versions of the Paygrades stored in employeeDB whereas the content of paygradeDB was accurate. To me, this indicated that the two separate EntityManagers were not communicating their changes to each other. Which is where my question comes in...

How do I ensure that any changes done to paygradeDB cascade into employeeDB?

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #3
#2
2017-08-10 14:43

Please try to demonstrate the issue with minimal one class runnable program, as explained in the posting instructions.

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,800
#3
2017-08-10 14:48

I'm not exactly sure how to do this to be honest. I'll try to see what I can do, but I won't be able to post for awhile since I'm not at my work computer (acquired files from cloud). Though, I can assure you the code given is the minimal amount that you'd need to see as the other pieces are just validation checks and queries apart from UI function handlers. I also know that my code handles updating a table accurately when the Overview controller is visible. That said - those updates take place within their respective EntityManager i.e. EmployeeOverview deals with employeeDB - PaygradeOverview deals with paygradeDB. Up until now I've never tried to make a change to paygradeDB and have it affect employeeDB - so I'm likely doing it wrong.

I'll try to construct a single class file that does all this, but I'm honestly not too confident I'll figure it out since I'm kind of new to JavaFX and ObjectDB.

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #4
#4
2017-08-10 19:13

To my knowledge there is no way to make a single class model of this example.
Note: I'm not a highly experienced developer in Java or ObjectDB so I may just not know how to do this...

Here's why...

Main:

  • Initializes EntityManagerFactory and EntityManagers
  • Provides a mechanism to change JavaFX Stages, Scenes, and other elements
    • Each call to a "show" function (ex: showPaygradeMenu() ) results in a transfer of control to a Controller class e.g. PaygradeMenuController.java via a FXML file targeting its controller class.

Controllers:

  • Control the flow of actions within the new view created from the FXML loaded.
  • Control the visual elements of the new view (Labels, TableView, etc).
  • Typically interact with Model classes (Entities with persistent data).
  • Pulls data from my personal Query class which contains all queries as function calls.

Models:

  • Provide constructors for the creation, validation, and modification of my persistent objects.
  • Uses my personal Query class for validation and checking of data.

To my knowledge I cannot make this work in a single class file primarily because of needing to maintain the Model classes as Entities to persist the data in my database. Another reason I don't find this possible is due to the fact that each view has its own FXML file associated with it which I'd have no idea how to implement inside of a fileName.java file...

What I want to know is...
How I can force an event in EntityManager paygradeDB to affect EntityManager employeeDB?

I know that paygradeDB is updated after I modify a value in it - employeeDB is not.

I also know that if I do the following...

  1. Run program
  2. Go to Manage Employees
  3. Go to Manage Paygrades
  4. Modify a Paygrade Object
  5. Go to Manage Employees

I will NOT see the change displayed in my TableView. However, if I...

  1. Run program
  2. Go to Manage Employees
  3. Go to Manage Paygrades (this exits Manage Employees)
  4. Modify a Paygrade Object
  5. End Program
  6. Run Program
  7. Go to Manage Employees

My changes in Manage Paygrades are displayed.

This means something either happens when my application starts or stops that unifies the changes in the different EntityManager objects I have created in Main. These are not actions that I have explicitly coded in - they are actions of ObjectDB that I don't know how to properly implement. These actions are what I'm wanting to know how to perform in my code so I can unify the databases when there is a modification made that needs to cascade throughout the entire application.

I don't want to tell my users "If you make a modification to anything - shut down the application and restart it" just so they can see their changes...

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #5
#5
2017-08-10 21:19

Just tried this...

public static void modifyPaygradeRate(double oldRate, double newRate)
{
  System.out.println("oldRate: " + oldRate + "\tnewRate: " + newRate);
 
  TypedQuery<Paygrade> query = Main.paygradeDB.createQuery("SELECT FROM Paygrade ORDER BY rate", Paygrade.class);
  TypedQuery<Paygrade> modifyTo = Main.paygradeDB.createQuery("UPDATE Paygrade SET rate = " + newRate + " WHERE rate = " + oldRate, Paygrade.class);
 
  query.getResultList().forEach(p -> System.out.println(p.getRate()));
 
  Main.paygradeDB.getTransaction().begin();
  modifyTo.executeUpdate();
  Main.paygradeDB.getTransaction().commit();
 
  System.out.println("--------");
 
  query.getResultList().forEach(p -> System.out.println(p.getRate()));
}

and got these results...

oldRate: 100.65 newRate: 8.65
9.1
9.6
10.6
11.1
13.1
15.1
100.65
--------
100.65
9.1
9.6
10.6
11.1
13.1
15.1

I'm not very sure how this could possibly be the case, but something isn't updating and I'm probably missing whatever it is that I need to do.

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #6
#6
2017-08-10 21:49

Regarding the single class test case, actually the requirement is a single top level class (sorry), with possibly additional inner classes, if needed.

In #5 the update is not visible because UPDATE queries in JPA bypasses the EntityManager, so it loss synchronization with the database content.

See this note on the top of the UPDATE query manual page:

Updating entity objects in the database using an UPDATE query may be slightly more efficient than retrieving entity objects and then updating them, but it should be used cautiously because bypassing the EntityManager may break its synchronization with the database. For example, the  EntityManager may not be aware that a cached entity object in its persistence context has been modified by an UPDATE query. Therefore, it is a good practice to use a separate EntityManager for UPDATE queries.

 

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,801
#7
2017-08-10 22:02

I'm not confident I'll be able to provide a single top level class with appropriate nested classes with how complex and interwoven this unit is. I may try pursuing that further if absolutely necessary, but I'd like to inquire further on your statement regarding the UPDATE queries since simply constructing this top level class will take many hours/days to do given my inexperience and the scope of this program and how it works with my FXML files.

Your statement of how the UPDATE queries cause a loss of synchronization with the EntityManager is curious. Is there a way to restore this synchronization through code so I can use UPDATE queries and still read those changes?

If UPDATE queries don't seem like a valid solution then I'm still likely looking for suggestions.

That said, I did come across a method that works one way but not the other...

If I query my employeeDB EntityManager for the first Employee with the Paygrade I'll be changing, and set its rate to the new rate that I want - then I get my EmployeeOverview TableView to update accurately, but my PaygradeOverview TableView fails to update. However, when I then try to update that Paygrade object so the table will update - I get an odd error message...

Caused by: com.objectdb.o._OptimisticLockException: Optimistic lock failed for object application.model.Paygrade#184 (object has version 86 instead of 87) [Paygrade:184]

Which I assume is because there is now a discrepancy between the Paygrade object I had initially and the Paygrade object I acquired from the Employee object - but I have no idea how to update both EntityManagers without getting this error :/

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #7
#8
2017-08-10 23:12

If the problem is an UPDATE query (as shown clearly on post #5 but was less clear from post #1) then there is no need for a test case as the situation is clear (if post #1 and post #5 deal with different issues - they should have been split to separate forum threads).

Losing synchronization when using UPDATE queries is not unique to ObjectDB but a limitation of all the JPA implementation.

Possible solutions:

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,802
#9
2017-08-10 23:24

Post #1 and #5 both deal with the same issue, but frankly - I was just looking for any bit of information regarding how I can update the elements within one EntityManager after a different EntityManager's objects underwent some change(s) when both EntityManagers share an Entity as an Object (employeeDB contains paygradeDB elements - I was modifying paygradeDB elements wanting employeeDB objects to be updated from those changes).

After playing around with toString() and tracing these objects' references around I found an interesting bit of information that I didn't realize before. Apparently the object references stored in each of my EntityManagers, employeeDB and paygradeDB, are actually to different objects entirely - which explains why the changes weren't cascading as I had expected them to. I came up with this idea and it worked...

    Main.employeeDB.getTransaction().begin();
 
    for(Employee e : Query.employeesByPaygrade(p)) //Returns only Employees with this Paygrade
     e.setPaygrade(p);                             //Update the Paygrade reference to an existing one
 
    p.setRate(newRate);                            //Update the Paygrade rate field
 
 /* This is what happens inside p.setRate() - summarized
  * ---------------------------------------------------------
  * Main.paygradeDB.getTransaction().begin();
  * this.rate = rate;
  * Main.paygradeDB.getTransaction().commit(); */
 
    Main.employeeDB.getTransaction().commit();

By updating the object references to point to the same object I could then update 'p' and all the changes take place naturally as intended.

That said - I don't like the fact that I'm having to cycle through each Employee object to update their references - so I'm going to give your em.clear(); idea a shot after using UPDATE to see if that'll do the trick.

At minimum I have "a" solution - though not ideal.

I'll let you know what I find next week as my shift is about over.

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #8
#10
2017-08-10 23:45

OK. Note that having a separate object context for every EntityManager is a very basic principle of JPA. See this manual page. You should have read it in the manual rather than finding it by exploring objects.

ObjectDB Support
ObjectDB - Fast Object Database for Java (JPA/JDO)
support
support's picture
Joined on 2010-05-03
User Post #2,803
#11
2017-08-14 20:43

From what I can tell the em.clear() suggestion does not allow me to unify the changes between EntityManagers (perhaps my implementation is incorrect). 

As I identified (and is noted in the manual you linked) - objects within two separate EntityManagers are in fact distinct objects and not one and the same even if persisted from a single object originally. That said, if I make a change to an object of one of these EntityManagers - the EM to which that object belongs originally is appropriately updated, but any other EM it may be associated with will not be updated. If I then try to update the other EM with the same object that was just updated by its EM - I receive a versioning error as the version of the object in one EM is now different from the version of the object in a different EM.

As mentioned - my solution is to unify the object references before updating the specific object needed. This works to unify changes between the two EMs. The downside to this is having to collect each object that needs to have its reference updated and then update the object itself. In my small scale situation this is not too cumbersome, but if this were expanded it would not be ideal.

I'll attempt once more to explain the original issue to see if you have any other suggestions - either way I have something that gets the job done for now.

Original Problem:
I have 2 EntityManagers that contain a distinct Entity each: Employee and Paygrade. Paygrade must remain unique and in its own EM, but Employee has a Paygrade Field. Thus, Employee and Paygrade are linked. If a change happens in Paygrade it needs to change in Employee, but not the other way around. I need a way to unify the change in Paygrade with Employee so that both EMs have the same data without having to re-load the application.

If the only way to accomplish this is as I've already described as my solution - then so be it. If there is in fact a better way to unify these changes I'll willingly attempt any other suggestions you may have.

DragonRulerX
DragonRulerX's picture
Joined on 2017-07-26
User Post #9

Post Reply

Please read carefully the posting instructions - before posting to the ObjectDB website.

  • You may have to disable pop up blocking in order to use the toolbar (e.g. in Chrome).
  • Use ctrl + right click to open the browser context menu in the editing area (e.g. for using a browser spell checker).
  • To insert formatted lines (e.g. Java code, stack trace) - select a style in the toolbar and then insert the text in the new created block.
  • Avoid overflow of published source code examples by breaking long lines.
  • You may mark in paragraph code words (e.g. class names) with the code style (can be applied by ctrl + D).
  • Long stack traces (> 50 lines) and complex source examples (> 100 lines) should be posted as attachments.
Attachments:
Maximum file size: 32 MB
Cancel