Friday, February 14. 2014
Weaving Problem in EclipseLink
This week I was called because a customer had an issue with EclipseLink and the colleague who usually works there was not in Spain. The problem was a weird one, and it was known, Mingtao explained it in his blog (my problem was inside a WebLogic Server too). The idea is simple: an object is read from the database, the entity manager is closed (so the object is detached), the read object is used as a template to create a new object changing only the ID (the primary key), the new object is merged using another manager. It seems that the specification specifically permits this procedure if the object is detached, but an exception was thrown in the last merge in the customer environment.
Although there was a clear code workaround (just copying the object instead of using the same one read previously) they insisted in knowing what was happening. So I decided to develop a simple test-case with a short entity object. I am going to write down all the steps I did (I consider that not only the solution is important, the process to reach that solution is also crucial):
A Sample entity with just three properties was created:
@Entity @Table(name="SAMPLE") public class Sample implements Serializable { @Id @Column(name = "ID") protected String id = null; @Column(name = "NAME") protected String name = null; @Column(name = "DESCRIPTION") protected String description = null;
A Servlet was developed to reproduce the problem. An initial sample object is read (created if it does not exists), then the manager is closed, in turn, the object detached:
out.println("Obtaining a detached sample..."); Sample s = new Sample(); s.setId("AC98"); em = emf.createEntityManager(); EntityTransaction entityTransaction = em.getTransaction(); entityTransaction.begin(); s = em.find(Sample.class, s.getId()); if (s == null) { out.println("Creating the sample cos it doesn't exist..."); s = new Sample(); s.setId("AC98"); s.setName("AC98"); s.setDescription("AC98"); s = (Sample) em.merge(s); s = em.find(Sample.class, s.getId()); } entityTransaction.commit(); em.close();
Using another manager the same object was merged with a different ID:
s.setId("AC99"); em = emf.createEntityManager(); entityTransaction = em.getTransaction(); entityTransaction.begin(); em.merge(s); entityTransaction.commit(); em.close();
The exception was thrown as expected:
Caused by: Exception [EclipseLink-7251] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException Exception Description: The attribute [id] of class [es.rickyepoderi.weaving.Sample] is mapped to a primary key column in the database. Updates are not allowed.
I decided to test the same without the server and, to my surprise, it worked. So if the same code was executed using a Java file (same entity and same code, but executed in a main method instead in the processing of the servlet request) the exception was not thrown.
First I thought that it was because of the jdbc resource (obviously in the Java example the EclipseLink was configured to use direct connections, using the driver manager, and inside the application server the persistence unit used a jdbc resource). But I deployed the servlet application using direct connections and it continued failing. It was another thing.
Looking carefully to the logs (the property eclipselink.logging.level was set to ALL) I realized that all the weaving properties were listed as set when using the WLS but they did not appear in the Java example logs. It seems that EclipseLink activates dynamic weaving if it detects a JavaEE container.
The next step was crystal clear. The persistence.xml file was modified disabling all weaving:
<property name="eclipselink.weaving" value="false"/>
And it worked! So the weaving was doing something strange that threw the exception. Checking the properties more carefully I discovered that only one property (eclipselink.weaving.internal) was the guilty one. So finally my little example inside the WLS worked with the following weaving configuration:
<property name="eclipselink.weaving.changetracking" value="true"/> <property name="eclipselink.weaving.lazy" value="true"/> <property name="eclipselink.weaving.eager" value="true"/> <property name="eclipselink.weaving.fetchgroups" value="true"/> <property name="eclipselink.weaving.internal" value="false"/>
I have to say that previously to this week I just know that weaving existed in EcliseLink, I knew it was a performance improvement (recommended for production) but I had no idea what it really was. Looking to EclipseLink documentation, it seems that the purpose of weaving consists of altering Java byte code for adding optimized JPA instructions that include lazy loading, change tracking, fetch groups and internal optimizations. So weaving changes the bytecode of the entity classes to optimize JPA performance (static performs the changes in the real classes, generating new ones, and dynamic performs them at execution time) and, obviously, something wrong was done in internal optimizations for my specific issue.
This morning I had the time to open a public bug against EclipseLink explaining the case (you know I try to be a good neighbor). I modified my external / Java project (weaving-error) to use static weaving, prepared it to execute against derbydb and opened a bug in eclipse bugzilla. In order to statically weave your entity classes you have to execute this java over the resulting jar file (the javax.persistence.jar was obtained from glassfish v4):
java -cp eclipselink-2.5.1.jar:javax.persistence.jar \ org.eclipse.persistence.tools.weaving.jpa.StaticWeave \ -persistenceinfo dist/weaving-error.jar -classpath dist/weaving-error.jar \ -loglevel ALL dist/weaving-error.jar weaving-error.jar
This java process performs the bytecode changes inside the entity classes following the configuration in the persistence.xml provided (in my case the same file of the project). With the resulting jar you can test the bug with this simple command (the derbydb database should be running in the default port):
java -cp weaving-error.jar:javax.persistence.jar:eclipselink-2.5.1.jar:derbyclient.jar \ es.rickyepoderi.weaving.Test
The bug is clear, if you perform the static weaving with internal activated, the exception is thrown. If your weaving is configured to do all optimizations except the internal ones, the test runs without problems. I saw a lot of references of this problem during the day I spent in the customer but no one explained the issue in detail and, of course, no one commented the problem was due to weaving (everybody changed the JPA implementation). It was a complicated day for sure. I hope that now, with this entry and the bug reported in EclipseLink, nobody wastes more time on this.
See you!
Comments