You are viewing a plain text version of this content. The canonical link for it is here.
Posted to users@openjpa.apache.org by "Landers, Richard" <Ri...@ct.gov> on 2008/01/14 18:42:35 UTC

RE: Does merge() handle deletes? -- Mystery solved

 
All,

I posted the message below a few weeks ago, and I've just now taken
another look.

I found a bug in my code that was causing the field change to 'null' to
be ignored!  (Imagine that :^)  Sloppiness with my test rig's
configuration made me think the problem existed ONLY with load-time
enhancement.  This just wasn't true.  

After fixing the bug, the application runs correctly with EITHER
compile-time or load-time class enhancement, as expected.  

So, for the record, I'm now having no problem at all with OpenJPA.

Thanks to the OpenJPA team for a quality product!

   --Rich

-----Original Message-----
From: Landers, Richard 
Sent: Wednesday, December 26, 2007 5:28 PM
To: 'users@openjpa.apache.org'
Subject: RE: Does merge() handle deletes?

Mike,

Thank you for your timely response!

Over the last couple of days I was finally able to spend the necessary
time to study your example and investigate further.  Here's what I
found...

Summary
=======

*  I am investigating a bug where a field of an entity, when set to
'null' goes unnoticed by the merge() operation.
	
*  When I set the field to any value but 'null' the merge operation
updates the entity's record as expected. 

*  When using load-time enhancement and deploying as part of a Web
application in Websphere 6.1, I see the bug.
	
*  When using compile-time enhancement in the same situation, I do not.

*  When using load-time enhancement but running in a stand-alone JVM, I
do not.

*  My environment is OpenJPA 1.1.1-SNAPSHOT and Spring 2.5 on Websphere
6.1.


Details
=======

Just so we were both on the same page, I upgraded to OpenJPA
1.1.1-SNAPSHOT (from 1.0.1), as in your example.  -- There was no change
in the behavior I'm seeing.

I'm not serializing my entities, everything is within one JVM.

You're showing explicit transaction control in your example.  I'm using
Spring's "@Transactional" declarative transactions.

So, to make sure it wasn't my Spring setup, I created an example that
uses my full Spring setup in a stand-alone Java program.  Here's the
main() routine:

--------------

    public static void main(String[] args) 
    throws Exception {
	log.info("Boot up the Spring applicationContext...");
	ServiceLocator services = new ServiceLocator();

	log.info("Retrieve @Transactional services needed...");
	OffenseCategoryService ocs =
services.getOffenseCategoryService();
	UserService us = services.getUserService(); 
	AuditRevisionService ars = services.getAuditRevisionService();

	log.info("Retrieve the '14-111n' OffenseCategory...");
	OffenseCategory category = ocs.findByName("14-111n");

	log.info("category.acdCode = " + category.getAcdCode());
	
	log.info("Set the OffenseCategory's ACD Code field to null...");
	category.setAcdCode(null);

	log.info("Merge the change back into the database...");
	User user = us.login("landers.richard", "landers.richard");
	AuditRevision ar = ars.createAuditRevision(user,
"MergeDeleteTest");
	ocs.merge(category, ar);

	log.info("Test complete.");
    }

---------------

Sorry the example is full of jargon!  Notes:

1. My ServiceLocator brings up the Spring application context.

2. An OffenseCategoryService is located, along with a UserService and
AuditRevisionService.

3. A particular OffenseCategory (entity) is retrieved by name.  This
method is annotated @Transactional, which means by the time it returns
it's value the transaction has ended, and the entity returned is
therefore detached.

4. The OffenseCategory's "ACD Code" field is set to null.

5. The OffenseCategory is merged with the database.  The stuff about the
User and AuditRevision are necessary for our system of maintaining an
audit trail.  Under the hood there's an EntityListener that sets the
auditRevision field on any entity OpenJPA is inserting or updating.

----------------

When I run this class, everything works as expected. The 'null' is
noticed by OpenJPA and the OffenseCategory is updated in the database
correctly.

I conclude from this that I'm using OpenJPA and Spring's @Transactional
correctly.

-----------------

However, when I run code that's substantially the same as part of my
web-app inside Websphere I still get the error I originally described.

Reading the documentation related to "DetachState", which you noted may
need to be set, I noticed that merging seems to rely on the
DetachedStateField.  And, further, that DetachedStateField seems to be
added when OpenJPA "enhances" my classes.

I discovered that I did not have a "LoadTimeWeaver" enabled in my Spring
setup of OpenJPA.  Buried in my logs there was a message indicating that
load-time class enhancement was not available.

I specified a LoadTimeWeaver in the Spring JPA section and added the
corresponding "-javaagent: spring-agent.jar" JVM command-line argument
to Websphere's JVM. Spring's log messages now indicate the
LoadTimeWeaver is available.

No change in behavior, though.  Still the OffenseCategory's ACD Code
field, when set to 'null', went unnoticed at merge() time.  

It's important to note that setting the ACD Code to anything but 'null'
works every time -- merge() notices the change and updates the
OffenseCategory record.

-----------------

At this point in the investigation, I am pretty sure there's a bug.

In an attempt to narrow it down further, I switched from load-time to
compile-time class enhancement using the
"org.apache.openjpa.ant.PCEnhancerTask".

And, miraculously, the bug disappeared.

Now I'm sure there's a bug.

------------------

I can try to come up with a smaller, more isolated, example that
demonstrates the bug, but I'm unsure what to try next.   Can you, or
anyone on the list, think of any way to narrow it down further?

  --Rich



-----Original Message-----
From: Michael Dick [mailto:michael.d.dick@gmail.com]
Sent: Thursday, December 20, 2007 5:02 PM
To: users@openjpa.apache.org
Subject: Re: Does merge() handle deletes?

Hi Rich,

Are you serializing/deserializing the entity? If so you'll need to add
the following property to persistence.xml :
<property name="openjpa.DetachState"
value="fgs(DetachedStateField=true)"/>

I have a similar set up that is working for me on OpenJPA 1.1.0-SNAPSHOT
:

        Customer c = em.find(Customer.class, id);

        em.clear();  // detach

        assertNotNull( c.getAddress());

        c.setAddress(null);

        em.getTransaction().begin();

        Customer cPrime = em.merge(c);

        em.getTransaction().commit();

        assertNull(c.getAddress());
        assertNull(cPrime.getAddress());

        c = em.find(Customer.class, id);
        assertNull(c.getAddress ());

Relevant entity code :
public class Customer extends TestEntity implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = -5752300027182374895L;

    @GeneratedValue(strategy = GenerationType.AUTO)
    @Id
    Long id;

    @Version
    @Column(name = "VERSN")
    protected int version;

    @ManyToOne(targetEntity = Address.class , cascade = CascadeType.ALL)
    Address address;

Which version of OpenJPA are you using?

-Mike

On Dec 19, 2007 2:34 PM, Landers, Richard < Richard.Landers@ct.gov>
wrote:

> Hello all,
>
> I'm having trouble using EntityManger.merge () operation...
>
> I have a entity A that holds a many-to-one reference to another, B. On

> entity A, the relationship is annotated like this:
>
> @ManyToOne(fetch=FetchType.EAGER, cascade=CascadeType.MERGE)
>
> At a certain point in processing, I've got a detached instance of A 
> referencing an instance of B.
>
> I want to dissociate A from any instance of B.  So I call:
>
>    a.setB(null);
>
> while A is detached, and then call:
>
>    merge(a)
>
> I thought the merge() operation would discover the change and update A

> in the database, but it does not.
>
> Is my mental model wrong?
>
> Do I have to (or *can* I) mark A as "dirty" to get OpenJPA to notice
it?
>
> Thanks in advance,
>
>  --Rich
>
>