Issue Details (XML | Word | Printable)

Key: GLASSFISH-3255
Type: Bug Bug
Status: Open Open
Priority: Minor Minor
Assignee: tware
Reporter: samdoyle
Votes: 0
Watchers: 0
Operations

If you were logged in you would be able to see more operations.
glassfish

OneToMany uni-directional relation doesn't always update jointable.

Created: 26/Jun/07 12:07 PM   Updated: 06/Mar/12 10:05 PM
Component/s: entity-persistence
Affects Version/s: 9.1pe
Fix Version/s: not determined

Time Tracking:
Not Specified

Environment:

Operating System: All
Platform: All


Issuezilla Id: 3,255
Tags:
Participants: gfbugbridge, pkrogh, samdoyle, Tom Mueller and tware


 Description  « Hide

Server 9.1 (build b41d-beta2)

Please see http://forums.java.net/jive/thread.jspa?threadID=27907&tstart=0 for
a detailed thread of the issue.

Basically when attempting to persist entities my jointable is only updated for
half the records leaving half the data orphaned. In order to get the jointable
to be updated I need to persist the exact same entity twice. Here is some code
to help reproduce:

Here is the messagebean that persists the data:


/*

  • GarageUpdateListenerBean.java
    *
  • Created on Jun 18, 2007, 1:38:00 PM
    *
  • To change this template, choose Tools | Template Manager
  • and open the template in the editor.
    */

package ejb.message;

import ejb.entity.GarageEntityBean;
import ejb.entity.GarageEntityBeanFacadeLocal;
import ejb.entity.GarageUpdateEntityBean;
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.EJB;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import messages.GarageUpdate;
import messages.MessageObject;
import messages.MessageObject.MessageType;

/**
*

  • @author sam
    */
    @MessageDriven(mappedName = "jms/GarageUpdateListenerBean", activationConfig =
    {
    @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue
    = "Auto-acknowledge"),
    @ActivationConfigProperty(propertyName = "destinationType", propertyValue
    = "javax.jms.Queue")
    })

public class GarageUpdateMessageDrivenBean implements MessageListener
{

@EJB
private GarageEntityBeanFacadeLocal garageEntityBeanFacade;

@Resource
private MessageDrivenContext mdc;

@PersistenceContext
private EntityManager em;

public GarageUpdateMessageDrivenBean()
{
}

public void onMessage( Message message )
{
ObjectMessage theMessage = null;
try
{
if ( message instanceof ObjectMessage )
{
theMessage = ( ObjectMessage )message;
Object payload = theMessage.getObject();
if ( ! ( payload instanceof MessageObject ) )

{ throw new JMSException( "UNKNOWN OBJECT MESSAGE RECEIVED!" ); }

MessageObject mo = ( MessageObject )payload;
MessageType mt = mo.getMessageType();

if ( mt != MessageType.GarageUpdate )

{ String error = "ERROR: Expected GarageUpdate message type" + " but received: " + mt; throw new JMSException( error ); }

GarageUpdate gu = ( GarageUpdate )mo;

GarageEntityBean targetBean =
garageEntityBeanFacade.find( gu.getGarageName() );

if( targetBean == null )

{ // No matching garage for this update. return; }

// Create a new entry
GarageUpdateEntityBean ge = new GarageUpdateEntityBean();
ge.setGarageName( gu.getGarageName() );
ge.setUsageCount( gu.getCurrentCount() );

targetBean.getGarageUpdates().add( ge );
em.merge( targetBean );
//em.persist( ge );
}
}
catch ( JMSException e )

{ e.printStackTrace(); mdc.setRollbackOnly(); }

catch ( Throwable te )

{ te.printStackTrace(); }

}

private void persist( Object object )

{ em.persist( object ); }

}


Here is the JMS test client to send the data.


/*

  • JMSTest2.java
    *
  • Created on June 18, 2007, 5:16 PM
    *
  • To change this template, choose Tools | Template Manager
  • and open the template in the editor.
    */

package jmstest;
import java.util.Hashtable;
import javax.jms.ConnectionFactory;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.ObjectMessage;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import messages.GarageUpdate;

/**
*

  • @author sdoyle
    */
    public class JMSTest2 {

Context ctx;

/** Creates a new instance of JMSTest2 */
public JMSTest2() {
Hashtable properties = new Hashtable(2);
properties.put(Context.PROVIDER_URL,"iiop://127.0.0.1:3701");
properties.put
(Context.INITIAL_CONTEXT_FACTORY,"com.sun.appserv.naming.S1ASCtxFactory");
try

{ ctx = new InitialContext(properties); }

catch (NamingException ex)
{ ex.printStackTrace(); }
}

public Object lookup(String name)
{
try
{ return ctx.lookup(name); }
catch (NamingException ex)
{ ex.printStackTrace(); } }
return null;
}

public static void main(String[] args)
{
JMSTest2 client = new JMSTest2();
GarageUpdate gu = new GarageUpdate();
gu.setGarageName( "Pier 39 Parking Garage" );
gu.setCurrentCount( new Integer( 147 ) );
try

{ ConnectionFactory connectionFactory = (ConnectionFactory)client.lookup ("jms/GarageUpdateListenerBeanFactory"); Queue queue = (Queue)client.lookup("jms/GarageUpdateListenerBean"); javax.jms.Connection connection = connectionFactory.createConnection (); javax.jms.Session session = connection.createSession (false,Session.AUTO_ACKNOWLEDGE); MessageProducer messageProducer = session.createProducer(queue); ObjectMessage om = session.createObjectMessage( ); om.setObject( gu ); messageProducer.send( om ); // Hack send additional message to get around jointable bug. messageProducer.send( om ); }

catch(Exception ex)

{ ex.printStackTrace(); System.exit( 1 ); }

System.exit( 0 );
}

}


Here is the garage entity and the stateless and local interface created by the
NetBeans IDE for access. It is in the GarageEntityBean where the relation is
defined.

/*

  • GarageEntityBean.java
    *
  • Created on June 19, 2007, 10:56 AM
    *
  • To change this template, choose Tools | Template Manager
  • and open the template in the editor.
    */

package ejb.entity;

import java.io.Serializable;
import java.util.Collection;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

/**

  • Entity class GarageEntityBean
    *
  • @author sam
    */

@Entity
@Table( name="garages" )
public class GarageEntityBean implements Serializable {

private String garageName;
private Double latitude;
private Double longitude;
private String entrance;
private String lotType;
private String operator;
private String facilityPhone;
private String officePhone;
private String rate;

private Collection<GarageUpdateEntityBean> garageUpdates;

/** Creates a new instance of GarageEntityBean */
public GarageEntityBean() {
}

/**

  • Returns a hash code value for the object. This implementation computes
  • a hash code value based on the id fields in this object.
  • @return a hash code value for this object.
    */
    @Override
    public int hashCode() { int hash = 0; hash += (this.getGarageName() != null ? this.getGarageName().hashCode() : 0); return hash; }

/**

  • Determines whether another object is equal to this GarageEntityBean. The
    result is
  • <code>true</code> if and only if the argument is not null and is a
    GarageEntityBean object that
  • has the same id field values as this object.
  • @param object the reference object with which to compare
  • @return <code>true</code> if this object is the same as the argument;
  • <code>false</code> otherwise.
    */
    @Override
    public boolean equals(Object object)
    Unknown macro: { // TODO}

return true;
}

/**

  • Returns a string representation of the object. This implementation
    constructs
  • that representation based on the id fields.
  • @return a string representation of the object.
    */
    @Override
    public String toString() { return "ejb.entity.GarageEntityBean[id=" + this.getGarageName() + "]"; }

@Id
@Column(name="GARAGENAME", unique=true)
public String getGarageName() { return garageName; }

public void setGarageName(String garageName) { this.garageName = garageName; }

@Column(name="LATITUDE")
public Double getLatitude() { return latitude; }

public void setLatitude(Double latitude) { this.latitude = latitude; }

@Column(name="LONGITUDE")
public Double getLongitude() { return longitude; }

public void setLongitude(Double longitude) { this.longitude = longitude; }

@Column(name="ENTRANCE")
public String getEntrance() { return entrance; }

public void setEntrance(String entrance) { this.entrance = entrance; }

@Column(name="LOT_TYPE")
public String getLotType() { return lotType; }

public void setLotType(String lotType) { this.lotType = lotType; }

@Column(name="OPERATOR")
public String getOperator() { return operator; }

public void setOperator(String operator) { this.operator = operator; }

@Column(name="FACILITY_PHONE")
public String getFacilityPhone() { return facilityPhone; }

public void setFacilityPhone(String facilityPhone) { this.facilityPhone = facilityPhone; }

@Column(name="OFFICE_PHONE")
public String getOfficePhone() { return officePhone; }

public void setOfficePhone(String officePhone) { this.officePhone = officePhone; }

@Column(name="RATE")
public String getRate() { return rate; }

public void setRate(String rate) { this.rate = rate; }

@OneToMany(cascade={CascadeType.ALL}, fetch=FetchType.EAGER)
@JoinTable(
name="garage_to_updates_join_table",
joinColumns={@JoinColumn(name="GARAGE_ID")},
inverseJoinColumns={@JoinColumn(name="GARAGE_UPDATE_ID")}
)
public Collection<GarageUpdateEntityBean> getGarageUpdates()
{ return garageUpdates; }
public void setGarageUpdates( Collection<GarageUpdateEntityBean>
garageUpdates )
{ this.garageUpdates = garageUpdates; }
}

----

facade

----

/*
* GarageEntityBeanFacade.java
*
* Created on June 19, 2007, 11:00 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package ejb.entity;

import java.util.Collection;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NamedQuery;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

/**
*
* @author sam
*/

/*
@NamedQueries
(
{ @NamedQuery ( name = "getLastGarageUpdate", query = "SELECT gu" + " FROM GarageEntityBean AS geb, IN( geb.garageUpdates ) gu" + " WHERE gu.id = MAX( gu.id ) AND geb = :garage" ) }
)
*/

@Stateless
public class GarageEntityBeanFacade implements GarageEntityBeanFacadeLocal {

@PersistenceContext
private EntityManager em;

private static final String QUERY_GET_LATEST_GARAGE_UPDATE_ID =
"SELECT MAX( gu.id ) " +
" FROM GarageEntityBean AS geb, IN ( geb.garageUpdates ) gu " +
" WHERE geb = :garage";

private static final String NAMED_QUERY_GET_GARAGE_UPDATES =
"SELECT gu" +
" FROM GarageEntityBean AS geb, IN ( geb.garageUpdates ) gu " +
" WHERE geb = :garage";

private static final String NAMED_QUERY_GET_LATEST_GARAGE =
"SELECT gu" +
" FROM GarageEntityBean AS geb, IN ( geb.garageUpdates ) gu " +
" WHERE geb = :garage AND gu.id = :update_id";

/** Creates a new instance of GarageEntityBeanFacade */
public GarageEntityBeanFacade() {
}

public void create(GarageEntityBean garageEntityBean) { em.persist(garageEntityBean); }

public void edit(GarageEntityBean garageEntityBean) { em.merge(garageEntityBean); }

public void destroy(GarageEntityBean garageEntityBean) { em.merge(garageEntityBean); em.remove(garageEntityBean); }

public GarageEntityBean find(Object pk) { return (GarageEntityBean) em.find(GarageEntityBean.class, pk); }

public List findAll() { return em.createQuery("select object(o) from GarageEntityBean as o").getResultList(); }

/**
* Get the latest garage update record for a given garage.
*/
public GarageUpdateEntityBean getLatestGarageUpdate( GarageEntityBean
garageEntityBean )
{
GarageUpdateEntityBean returnValue = null;
int currentRecord = 0;

try
{
Query q = em.createQuery( NAMED_QUERY_GET_GARAGE_UPDATES );
q.setParameter( "garage", garageEntityBean );
List<GarageUpdateEntityBean> updatesList = q.getResultList();

for ( GarageUpdateEntityBean gu : updatesList )
{
if ( gu.getId() > currentRecord )
{ currentRecord = gu.getId(); returnValue = gu; }
}
}
catch( Exception e )
{ e.printStackTrace(); }

return returnValue;
}
}

----

local interface

----

/*
* GarageEntityBeanFacadeLocal.java
*
* Created on June 19, 2007, 11:00 AM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package ejb.entity;

import java.util.List;
import javax.ejb.Local;

/**
*
* @author sam
*/
@Local
public interface GarageEntityBeanFacadeLocal {

void create(GarageEntityBean garageEntityBean);

void edit( GarageEntityBean garageEntityBean );

void destroy( GarageEntityBean garageEntityBean );

GarageEntityBean find( Object pk );

List findAll();

GarageUpdateEntityBean getLatestGarageUpdate( GarageEntityBean
garageEntityBean ) ;
}


----

GarageUpdateEntityBean

Many of these to One GarageEntity

----

/*
* GarageEntity.java
*
* Created on June 18, 2007, 4:17 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

/*
* GarageEntity.java
*
* Created on Jun 18, 2007, 1:30:54 PM
*
* To change this template, choose Tools | Template Manager
* and open the template in the editor.
*/

package ejb.entity;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

/**
*
* @author sam
*/
@Entity
@Table(name = "garageupdates")
public class GarageUpdateEntityBean implements Serializable
{

private static final long serialVersionUID = 1L;

private Integer id;
private String garageName;
private Integer usageCount;

public GarageUpdateEntityBean()
{
}

@Id
@Column(name="ID", unique=true)
@GeneratedValue( strategy = GenerationType.AUTO )
public Integer getId( )
{ return id; }

public void setId( Integer id )
{ this.id = id; }

@Column(name="GARAGENAME")
public String getGarageName( )
{ return garageName; } }

public void setGarageName( String garageName )

{ this.garageName = garageName; }

@Column(name="USAGECOUNT")
public Integer getUsageCount( )

{ return usageCount; }

public void setUsageCount( Integer usageCount )

{ this.usageCount = usageCount; }

}



gfbugbridge added a comment - 26/Jun/07 05:01 PM

<BT6574309>


pkrogh added a comment - 27/Jun/07 05:52 AM

Hi,

The attached code confuses me somewhat. This is what I think it does:
Sends a 'Message' with the information: name = "Pier 39 Parking Garage" count
= new Integer( 147 ).

a Find is done for a GarageEntityBean with the name "Pier 39 Parking Garage".

A new GarageUpdateEntityBean is created with the name "Pier 39 Parking Garage"
count = new Integer( 147 ) - a new sequence number will be generated.
this EntityBean is then added to the collection.

Is what you are seeing that only half (roughly every second add) of these get
added to the join table? What do you mean by that, because this test case
(assuming you remove the hack) only adds one element to the collection. Do you
mean that if you run the application twice, only one will be added?

NOTE: the hack confuses me as it appears that what will happen is a brand new
GarageUpdateEntityBean will be created with the same count and name but a
different sequence number.

One this that I notice that mean be causing some issues, is your override of
equals and hashcode. Can you remove that and see if your problem is solved?

Thanks


samdoyle added a comment - 27/Jun/07 10:10 AM

"Is what you are seeing that only half (roughly every second add) of these get
added to the join table? What do you mean by that, because this test case
(assuming you remove the hack) only adds one element to the collection. Do you
mean that if you run the application twice, only one will be added?"

Precisely! That is the whole purpose of this bug. The jointable is only updated
once when it should be updated twice. I think it might be defined more clearly
in the link to the discussion thread I posted here.

S.D.


pkrogh added a comment - 27/Jun/07 01:00 PM

What happens when you remove the .equals and .hashcode overrides?

NOTE: I am currently attempting to recreate your issue and even with
the .equals and .hashcode overides I am not able to....


samdoyle added a comment - 27/Jun/07 01:15 PM

With the code I have attached using the version I mentioned and MySQL 5.0.41
this problem is always reproducible.

S.D.


pkrogh added a comment - 27/Jun/07 05:57 PM

Please let me know what happens when you remove the equals and hashtable
overrides.


samdoyle added a comment - 28/Jun/07 10:07 AM

"Please let me know what happens when you remove the equals and hashtable
overrides."

Please explain to me how I am supposed to locate the target entity without
having the equals method overriden since my JMS message contains the name of
the entity and not the entity itself.

Thanks, S.D.


samdoyle added a comment - 28/Jun/07 10:27 AM

I'll change my test so that I get all of them and manually go through all
myself. I'll let you know the results after doing this and removing the
override.


samdoyle added a comment - 28/Jun/07 10:35 AM

After performing the test removing the overrides on the hash and equal all the
records are now appearing in the jointable and not just half.

S.D.


tware added a comment - 28/Jun/07 11:19 AM

Some additional questions:

  • I am wondering if you can provide a little more detail about how this code
    runs. You mention a bunch of data in your forum post:

2 300 Pier 39 Parking Garage
52 300 Pier 39 Parking Garage
102 500 Pier 39 Parking Garage
53 500 Pier 39 Parking Garage
152 122 Pier 39 Parking Garage
54 122 Pier 39 Parking Garage
202 122 Pier 39 Parking Garage

-Do you modify the client to change the counts?
-Is there any chance of a race condition. i.e. is the processing for each call
completely finish by the time you start the next one?
-When you have your overridden equals and hashcode do you always get the same
bean from the following code?

GarageEntityBean targetBean =
garageEntityBeanFacade.find( gu.getGarageName() );


tware added a comment - 28/Jun/07 11:26 AM

One more question:

If you turn on logging, how does the SQL you see look?

(to log the sql, add the persistence unit property toplink.logging.leve=FINER)


samdoyle added a comment - 28/Jun/07 01:00 PM

"Some additional questions:

  • I am wondering if you can provide a little more detail about how this code
    runs. You mention a bunch of data in your forum post:

2 300 Pier 39 Parking Garage
52 300 Pier 39 Parking Garage
102 500 Pier 39 Parking Garage
53 500 Pier 39 Parking Garage
152 122 Pier 39 Parking Garage
54 122 Pier 39 Parking Garage
202 122 Pier 39 Parking Garage

-Do you modify the client to change the counts?
-Is there any chance of a race condition. i.e. is the processing for each call
completely finish by the time you start the next one?
-When you have your overridden equals and hashcode do you always get the same
bean from the following code?

GarageEntityBean targetBean =
garageEntityBeanFacade.find( gu.getGarageName() );"

1.) Yes I do modify the code.
2.) I ran the test client to full completion before changing and sending the
next data set.
3.) Yes there is only one entity bean that can be matched and it was always
located when tracing through the debugger.


pkrogh added a comment - 29/Jun/07 12:58 PM

Hi, I'm downgrading the issue to a P3 now that there is a workaround. Note -
still believe this to be an important issue and plan to continue working on it.

I am still having trouble reproducing your issue. I was wondering if you could
attach your ear to the bug so we can deploy your app.

Also how is your database populated prior to running this test?


samdoyle added a comment - 29/Jun/07 02:42 PM

Hello and thanks for the workaround.

The database prior to running the tests only contains the garage entries. The
updates are generated via the test client. I'm not back in the office until
Monday but will attach the ear then.

Thanks, S.D.


pkrogh added a comment - 04/Jul/07 06:13 AM

We are currently working to closedown P3 bugs, would it be possible to get the
reproduction ear by COB Thursday July 5th? Thanks.

Peter


pkrogh added a comment - 10/Jul/07 06:28 AM

Downgrading. I believe that the customer can reproduce this, but I am not able
to without help.


pkrogh added a comment - 10/Jul/07 06:32 AM

starting issue


Tom Mueller added a comment - 06/Mar/12 10:05 PM

Bulk change to set fix version to "not determined" where the issue is open but the value is for a released version.