|
Comments
|
Today's Top SOA Links
Features JPA Under the Hood
Understanding the Dynamics of Your JPA Framework
By: Alois Reitbauer
Nov. 24, 2009 01:17 PM
Java Developer Magazine on Ulitzer I recently spoke on the behavior of different JPA frameworks at W-JAX(Germany) and TheServerSide Java Symposium (Prague). As some people have asked me, I am publishing the samples as well. I would also give away the Eclipse project, however, with all the third-party libraries I am sure I will end up not doing it correctly. In addition, I can add some comments on the samples and why they are as they are The goal of my experiment was to compare different JPA frameworks regarding their runtime characteristics. I addressed the following points:
Preparation - SQL Scripts, Entity Classes and Persistence Unit Definitions
Next we need to define the persistence classes. We define a User class and an Account class. Getter and setter methods are omitted for brevity here. So far no rocket science. In the next step we define the persistence units. I defined a single unit per persistence provider. According to the JPA spec this should work fine. However some strange things might happen
That's it for preparation now we are ready to look at the samples, which will help us to understand the inner workings of JPA frameworks. Dynamic Behaviour of JPA Frameworks Sample 1- It depends on what you make out of it public static void simpleLoadSample() { EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=1"); iterateOverItems(query.getResultList()); em.close(); } Actually a JPA framework should produce the same SQL statement as for the code below. public static void simpleLoadwithParameter() { EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=?"); query.setParameter(1, 1L); iterateOverItems(query.getResultList()); em.close(); } In my tests both - OpenJPA and EclipseLink - create proper prepared statements. However Hibernate creates a statement that looks like this "select ... from user where id=1″ and also prepares this statement. Prepared statements like this can have render PreparedStatement caching as well as database query caching obsolete. Sample 2 - The Magic Value public static void loadTwiceWithQuery (){ EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=1"); iterateOverItems(query.getResultList()); em.close(); try { System.in.read(); } catch (IOException e) { e.printStackTrace(); } em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); query = em.createQuery("select u from User u where u.id=1"); iterateOverItems(query.getResultList()); em.close(); } When trying this example with different JPA frameworks you will see that two database queries will be executed unless query caching is enabled. However the second query will return the object with the "old" values. Why that? The query is used to retrieve the id of the user. As it realized that the object has already been loaded it will not construct that object again. In case you always want the latest state, you would have to use refresh(). Sample 3 - Staying up to date public static void simpleUpdate (){ EntityManager em1= EntityManagerUtil.getEMFactory(provider).createEntityManager(); em1.getTransaction().begin(); User user = em1.find(User.class, 1L); user.setFirstName("otherFirstName" + System.currentTimeMillis()); em1.getTransaction().commit(); em1.close (); } Guess what happens ... the object get's updated Sample 4 - Having good references Get an instance, whose state may be lazily fetched. ... The application should not expect that the instance state will be available upon detachment, unless it was accessed by the application while the entity manager was open. Hmmm, I do not know how you feel about this, but the word may confused me here a bit. Actually this means I do not know whether the object will be fetched or not. I used the following code sample to see what's happening public static void getReferenceSample (){ EntityManager em= EntityManagerUtil.getEMFactory(provider).createEntityManager(); em.getReference(User.class, 1L); em.close (); } Here my experiments show that eclipseLink loads the data while Hibernate and OpenJPA do not load the data. Sample 5 - Staying in good relations public static void loadRelationSample () { EntityManager em= EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select acc from Account acc where acc.id = 1"); Account account = (Account) query.getSingleResult(); User user = account.getUser(); em.close (); } Very interestingly all frameworks I used by default load the master record as well. How they actually do this depends on the framework as well as the database used. OpenJPA for example by default uses a join, eclipseLink does not and when using Hibernate it depends on the used dialect (and database). Sample 6 - Yam Session public static void checkMaxSessions() { ArrayList<EntityManager> myEMs = new ArrayList<EntityManager>(); for (int i = 1; i < 51; ++i) { try { EntityManager em = EntityManagerUtil.getEMFactory(provider) .createEntityManager(); myEMs.add(em); User u = (User) em.find(User.class, new Long(i)); u.getFirstName(); System.out.println("Concurrent sessions: " + i); } catch (Exception ex) { System.err.println(ex); break; } try { Thread.sleep(700); } catch (InterruptedException e) { } } } public static void checkMaxSessionsWithTransaction() { ArrayList<EntityManager> myEMs = new ArrayList<EntityManager>(); for (int i = 1; i < 51; ++i) { try { EntityManager em = EntityManagerUtil.getEMFactory(provider) .createEntityManager(); myEMs.add(em); em.getTransaction().begin(); User u = (User) em.find(User.class, new Long(i)); u.getFirstName(); System.out.println("Concurrent sessions: " + (i)); } catch (Exception ex) { System.err.println(ex); break; } try { Thread.sleep(300); } catch (InterruptedException e) { } } } EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=1″); iterateOverItems(query.getResultList()); em.close(); } public static void simpleLoadwithParameter() { EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=?"); query.setParameter(1, 1L); iterateOverItems(query.getResultList()); em.close(); } public static void loadTwiceWithQuery (){ EntityManager em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select u from User u where u.id=1″); iterateOverItems(query.getResultList()); em.close(); try { System.in.read(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } em = EntityManagerUtil.getEMFactory(provider).createEntityManager(); query = em.createQuery("select u from User u where u.id=1″); /* 2 */ iterateOverItems(query.getResultList()); em.close(); } public static void simpleUpdate (){ EntityManager em1= EntityManagerUtil.getEMFactory(provider).createEntityManager(); em1.getTransaction().begin(); User user = em1.find(User.class, 1L); user.setFirstName("otherFirstName" + System.currentTimeMillis()); // em1.persist(user); em1.getTransaction().commit(); em1.close (); } public static void updateToSameValue (){ EntityManager em1= EntityManagerUtil.getEMFactory(provider).createEntityManager(); em1.getTransaction().begin(); User user = em1.find(User.class, 1L); user.setFirstName(user.getFirstName()); em1.persist(user); em1.getTransaction().commit(); em1.close (); } public static void getReferenceSample (){ EntityManager em= EntityManagerUtil.getEMFactory(provider).createEntityManager(); em.getReference(User.class, 1L); }
public static void loadRelationSample () { EntityManager em= EntityManagerUtil.getEMFactory(provider).createEntityManager(); Query query = em.createQuery("select acc from Account acc where acc.id = 1″); Account account = (Account) query.getSingleResult(); User user = account.getUser(); em.close (); } public static void checkMaxSessions() { ArrayList<EntityManager> myEMs = new ArrayList<EntityManager>(); for (int i = 1; i < 51; ++i) { try { EntityManager em = EntityManagerUtil.getEMFactory(provider) .createEntityManager(); myEMs.add(em); User u = (User) em.find(User.class, new Long(i)); u.getFirstName(); System.out.println("Concurrent sessions: " + i); } catch (Exception ex) { System.err.println(ex); break; } try { Thread.sleep(700); } catch (InterruptedException e) { } } } public static void checkMaxSessionsWithTransaction() { ArrayList<EntityManager> myEMs = new ArrayList<EntityManager>(); for (int i = 1; i < 51; ++i) { try { EntityManager em = EntityManagerUtil.getEMFactory(provider) .createEntityManager(); myEMs.add(em); em.getTransaction().begin(); User u = (User) em.find(User.class, new Long(i)); u.getFirstName(); System.out.println("Concurrent sessions: " + (i)); } catch (Exception ex) { System.err.println(ex); break; } try { Thread.sleep(300); } catch (InterruptedException e) { } } } What we can see here that when using no transactions, we can do all the work with one connection. When we use transactions however Hibernate will open a new connection per EntityManager. So, if you do not need transactions - when you just load a list on a website for example - you are better off not using them. Conclusion Further Reading Related reading:
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week |
|||||||||||||||||||||||||||