0 Replies Latest reply on Nov 13, 2007 11:18 AM by ypsilon

    CMP field with foreign key and CMR in the same bean leads to

    ypsilon Newbie

      Hello.

      I am facing a weired problem here (JBoss AS 4.0.5 GA ). We have a setup in which several entity beans have CMR-Fields which are exposed as normal CMP fields as well (basically the same way as described here: http://www.jboss.com/index.html?module=bb&op=viewtopic&t=57895). When I update the relation as in such a scenario the changes to the foreign key field are not persisted if I perform a read operation after the update to the field. Sounds confusing? Here is some code to illustrate what I do.

      We have a CustomerBean which references an AddressBean:

      public abstract class CustomerBean implements EntityBean {
       /**
       * @ejb.pk-field
       * @sql.notnull name=CUS_ID_NN
       * @ejb.persistence column-name="id"
       * @ejb.interface-method
       */
       public abstract Long getId();
      
       /**
       * @ejb.interface-method
       * @ejb.transaction type="Required"
       */
       public abstract void setId(Long s);
      ...
       /**
       * @ejb.interface-method view-type="local"
       * @ejb.relation name="Customer contains an address" role-name="Customer-Address" target-ejb="Address"
       * target-role-name="Address-belongs-to-Customer" target-multiple="no"
       * @jboss.relation fk-column="addressId" related-pk-field="id"
       * @jboss.relation-mapping style="foreign-key"
       * @ejb.value-object aggregate="sample.AddressValue" aggregate-name="AddressValue"
       * members="sample.AddressLocal" members-name="Address" relation="external"
       * @sql.skip
       */
       public abstract AddressLocal getAddress();
      
       /**
       * @ejb.transaction type="Required"
       * @ejb.interface-method view-type="local"
       */
       public abstract void setAddress(AddressLocal Address);
      
       /**
       * @sql.notnull name=
       * @ejb.persistence column-name="addressId"
       * @ejb.interface-method
       */
       public abstract Long getAddressId();
      
       /**
       * @ejb.interface-method
       * @ejb.transaction type="Required"
       */
       public abstract void setAddressId(Long s);
      
      ...
      }
      

      This would be the AddressBean:
      public abstract class AddressBean implements EntityBean {
       /**
       * @ejb.pk-field
       * @ejb.persistence column-name="id"
       * @ejb.interface-method
       * @sql.primarykey name=ADD_PK
       */
       public abstract Long getId();
      
       /**
       * @ejb.interface-method
       * @ejb.transaction type="Required"
       */
       public abstract void setId(Long s);
      
       /**
       * @ejb.persistence column-name="salutation"
       * @ejb.interface-method
       */
       public abstract String getSalutation();
      
       /**
       * @ejb.interface-method
       * @ejb.transaction type="Required"
       */
       public abstract void setSalutation(String s);
      
      ...
      
      }
      

      And finally some SessionBean which performs the update:
      public abstract class CustomerMangementBean implements javax.ejb.SessionBean {
      ...
       /**
       * @ejb.interface-method view-type="remote"
       * @ejb.transaction type="Required"
       *
       * @return
       */
       public CustomerValue updateCustomer(CustomerValue cv, boolean trashTheRelation ) {
       try {
       AddressLocal al = AddressUtil.getLocalHome().create(cv.getAddressValue());
       cv.setAddressId(al.getId());
       CustomerLocal cl = CustomerUtil.getLocalHome().findByPrimaryKey(new CustomerPK(cv.getId()));
       cl.setAddressId(al.getId());
       cl.setSignStamp(cv.getSignStamp());
       if (trashTheRelation ) {
       log.debug("this will trash the CMR field: " + cl.getAddress());
       }
       return cv;
       } catch (CreateException e) {
       log.debug("Error in CustomerMangementBean#updateCustomer", e);
       throw new EJBException("Error in CustomerMangementBean#updateCustomer", e);
       } catch (NamingException e) {
       log.debug("Error in CustomerMangementBean#updateCustomer", e);
       throw new EJBException("Error in CustomerMangementBean#updateCustomer", e);
       } catch (FinderException e) {
       log.debug("Error in CustomerMangementBean#updateCustomer", e);
       throw new EJBException("Error in CustomerMangementBean#updateCustomer", e);
       }
       }
      ...
      


      The generated xml files look like this:

      ejb-jar.xml

       <entity >
       <description><![CDATA[User: phartmann Date: Nov 8, 2007 Time: 9:16:30 AM]]></description>
      
       <ejb-name>Customer</ejb-name>
      
       <local-home>sample.CustomerLocalHome</local-home>
       <local>sample.CustomerLocal</local>
      
       <ejb-class>sample.CustomerCMP</ejb-class>
       <persistence-type>Container</persistence-type>
       <prim-key-class>sample.CustomerPK</prim-key-class>
       <reentrant>false</reentrant>
       <cmp-version>2.x</cmp-version>
       <abstract-schema-name>Customer</abstract-schema-name>
       <cmp-field >
       <description><![CDATA[]]></description>
       <field-name>id</field-name>
       </cmp-field>
       <cmp-field >
       <description><![CDATA[]]></description>
       <field-name>signStamp</field-name>
       </cmp-field>
       <cmp-field >
       <description><![CDATA[]]></description>
       <field-name>addressId</field-name>
       </cmp-field>
      
       <query>
       <query-method>
       <method-name>findAll</method-name>
       <method-params>
       </method-params>
       </query-method>
       <ejb-ql><![CDATA[SELECT OBJECT(a) FROM Customer a]]></ejb-ql>
       </query>
       <!-- Write a file named ejb-finders-CustomerBean.xml if you want to define extra finders. -->
      
      ...
      
       <!-- Relationships -->
       <relationships >
       <ejb-relation >
       <ejb-relation-name>Customer contains an address</ejb-relation-name>
      
       <ejb-relationship-role >
       <ejb-relationship-role-name>Customer-Address</ejb-relationship-role-name>
       <multiplicity>One</multiplicity>
       <relationship-role-source >
       <ejb-name>Customer</ejb-name>
       </relationship-role-source>
       <cmr-field >
       <cmr-field-name>address</cmr-field-name>
       </cmr-field>
       </ejb-relationship-role>
      
       <ejb-relationship-role >
       <ejb-relationship-role-name>Address-belongs-to-Customer</ejb-relationship-role-name>
       <multiplicity>One</multiplicity>
       <relationship-role-source >
       <ejb-name>Address</ejb-name>
       </relationship-role-source>
       </ejb-relationship-role>
      
       </ejb-relation>
       <!--
       To add relationships for beans not managed by XDoclet, add
       a file to your XDoclet merge directory called relationships.xml that contains
       the <ejb-relation></ejb-relation> markups for those beans.
       -->
       </relationships>
      
       </entity>
      
      


      jbosscmp-jdbc.xml
      ...
       <relationships>
       <ejb-relation>
       <ejb-relation-name>Customer contains an address</ejb-relation-name>
       <foreign-key-mapping/>
      
       <ejb-relationship-role>
       <ejb-relationship-role-name>Customer-Address</ejb-relationship-role-name>
       <key-fields/>
      
       </ejb-relationship-role>
       <ejb-relationship-role>
       <ejb-relationship-role-name>Address-belongs-to-Customer</ejb-relationship-role-name>
       <key-fields>
       <key-field>
       <field-name>id</field-name>
       <column-name>addressId</column-name>
       </key-field>
       </key-fields>
      
       </ejb-relationship-role>
       </ejb-relation>
       <!--
       To add jboss relationships for beans not managed by XDoclet, add
       a file to your XDoclet merge directory called jbosscmp-jdbc-relationships.xml that contains
       the <ejb-relation></ejb-relation> markups for those beans.
       -->
       </relationships>
      
      ...
      



      And, not to forget the obligatory test-code:

      CustomerManagement customerManagement = CustomerManagementUtil.getHome(environment).create();
      CustomerValue cv = new CustomerValue(new Long(id), new java.sql.Timestamp(System.currentTimeMillis()), null);
      AddressValue av2 = new AddressValue();
      av2.setFirstName("Joe");
      av2.setLastName("Dalton");
      av2.setStreet("wallstreet 23a");
      av2.setZipcode("12346");
      av2.setCity("Dasytown");
      cv.setAddressValue(av2);
      customerManagement.updateCustomer(cv, true);
      
      



      Here is what the tests reveal:

      Before running the test class with the parameter trashTheRelation set to false
      ejb=> select * from customer;
       id | signstamp | addressid
      ---------------+------------+-----------
       1194955070209 | 2007-11-13 |
      (1 row)
      


      Server.log looks as follows:

      2007-11-13 16:00:21,559 13163267 INFO [PtxLogInterceptor] () Invocation of Method updateCustomer is identified as 6021559
      2007-11-13 16:00:21,563 13163271 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand.Address] (CALLID[6021559]) Executing SQL: SELECT COUNT(*) FROM address WHERE id=?
      2007-11-13 16:00:21,566 13163274 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand.Address] (CALLID[6021559]) Executing SQL: INSERT INTO address (id, salutation, firstName, lastName, street, houseNumber, addendum, zipcode, city, country, phoneNumber, mobileNumber, email, hiddenemail, isValidated, postBoxBased, robinson, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      2007-11-13 16:00:21,573 13163281 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.Customer#findByPrimaryKey] (CALLID[6021559]) Executing SQL: SELECT t0_Customer.id FROM customer t0_Customer WHERE t0_Customer.id=?
      2007-11-13 16:00:21,577 13163285 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCLoadEntityCommand.Customer] (CALLID[6021559]) Executing SQL: SELECT SignStamp, addressId FROM customer WHERE (id=?)
      2007-11-13 16:00:21,581 13163289 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.Address#findByPrimaryKey] (CALLID[6021559]) Executing SQL: SELECT t0_Address.id FROM address t0_Address WHERE t0_Address.id=?
      2007-11-13 16:00:21,584 13163292 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreEntityCommand.Customer] (CALLID[6021559]) Executing SQL: UPDATE customer SET SignStamp=?, addressId=? WHERE id=?
      2007-11-13 16:00:21,655 13163363 INFO [PtxLogInterceptor] (CALLID[6021559]) Invocation of Method updateCustomer, identified as 6021559 ended


      ...and afterwards:
      ejb=> select * from customer;
       id | signstamp | addressid
      ---------------+------------+---------------
       1194955070209 | 2007-11-13 | 1194966021563
      (1 row)
      


      Great! The relation is set as expected!

      and now the retry with the parameter set to true. First prepare the database.
      ejb=> update customer set addressid = null;
      UPDATE 1
      ejb=> select * from customer;
       id | signstamp | addressid
      ---------------+------------+-----------
       1194955070209 | 2007-11-13 |
      (1 row)
      


      as you can see: AddressId is null. Now I run the client again and set the parameter to true so that the relation is not persisted for some unknown reason. Here is the server log for that try:

      2007-11-13 16:11:27,565 13829273 INFO [PtxLogInterceptor] () Invocation of Method updateCustomer is identified as 6687565
      2007-11-13 16:11:27,571 13829279 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand.Address] (CALLID[6687565]) Executing SQL: SELECT COUNT(*) FROM address WHERE id=?
      2007-11-13 16:11:27,578 13829286 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCCreateEntityCommand.Address] (CALLID[6687565]) Executing SQL: INSERT INTO address (id, salutation, firstName, lastName, street, houseNumber, addendum, zipcode, city, country, phoneNumber, mobileNumber, email, hiddenemail, isValidated, postBoxBased, robinson, company) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
      2007-11-13 16:11:27,586 13829294 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.Customer#findByPrimaryKey] (CALLID[6687565]) Executing SQL: SELECT t0_Customer.id FROM customer t0_Customer WHERE t0_Customer.id=?
      2007-11-13 16:11:27,593 13829301 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCLoadEntityCommand.Customer] (CALLID[6687565]) Executing SQL: SELECT SignStamp, addressId FROM customer WHERE (id=?)
      2007-11-13 16:11:27,600 13829308 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.Address#findByPrimaryKey] (CALLID[6687565]) Executing SQL: SELECT t0_Address.id FROM address t0_Address WHERE t0_Address.id=?
      2007-11-13 16:11:27,604 13829312 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCFindByPrimaryKeyQuery.Address#findByPrimaryKey] (CALLID[6687565]) Executing SQL: SELECT t0_Address.id FROM address t0_Address WHERE t0_Address.id=?
      2007-11-13 16:11:27,608 13829316 DEBUG [sample.CustomerMangementBean] (CALLID[6687565]) this will trash the CMR field: AddressLocal:[.1194966687571.]

      2007-11-13 16:11:27,609 13829317 DEBUG [org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreEntityCommand.Customer] (CALLID[6687565]) Executing SQL: UPDATE customer SET SignStamp=? WHERE id=?
      2007-11-13 16:11:27,638 13829346 INFO [PtxLogInterceptor] (CALLID[6687565]) Invocation of Method updateCustomer, identified as 6687565 ended

      let's query the DB again...
      ejb=> select * from customer;
       id | signstamp | addressid
      ---------------+------------+-----------
       1194955070209 | 2007-11-13 |
      (1 row)
      

      again null! Why??

      Some things to notice:
      - when performing the log output which destroys the relation the correct id is returned as you can see from the highlighted line in the log
      - the addresses which are added here end up in the database.
      - other changes (let's say change the customer's name) are persisted as well.
      - no exception in any form is reported
      - returning the id field of the customer does not seem to destroy the relation
      - when I remove the AddressId fields or the Relation fields the problem disappears. (But that is hardly an option for some reasons I do not intend to discuss here.)
      - changing the code from
       cl.setAddressId(al.getId());
      


      to

       cl.setAddress(al);
      

      changes the behaviour: Now no update will succeed in any case.
      - this does not seem to be a database issue as the example will perform alike on oracle and postgresql databases

      To sum it up: If you have a bean with an FK-field and a CMR field which reflects the fk-field and you update the fk-field any further read operation on that will void your changes.

      Is that behaviour intended? What can I do against that?

      Any help is appreciated.

      Thanks in advance.
      Greetings
      PeeR