WebSphere and CMRs

Discussions

EJB programming & troubleshooting: WebSphere and CMRs

  1. WebSphere and CMRs (9 messages)

    I am developing on WSAD 5.0EA/WebSphere App Server 5.0 using DB2. I am running into problems or questions about how to code the container managed relationships between several beans. I am using CMP. I have existing db tables and am trying to do a meet in the middle mapping between the beans and the tables. The tables look similar to the following:

    Profile

    profile_id long(pk) not null
    version String

    Profile_Specific

    profile_spec_id long (pk) not null
    profile_id long (fk to Profile) not null
    name String
    version String

    I am running into problems because the foreign key profile_id in the Profile_Specific table can not be null. We do not want to create a profile_specific table entry without it's corresponding profile entry.

    I have been reading/searching around on how to initialize CMRs and it seems like the recommended way is to do this in the ejbPostCreate method. This works fine as long as my fk field can be null. But when I change my schema to non null, it fails.

    I have read about a weblogic parameter called delay-database-insert-until which sounds like what I need. Does anyone know if WebSphere has a similar mechanism for delaying the database insert until after the ejbPostCreate has been called? Or does anyone have any other ideas on how to do this in WebSphere??

    Thanks,
    Aimee
  2. WebSphere and CMRs[ Go to top ]

    Any 'required" field will have to be initialized in ejbCreate, not ejbPostCreate.

    You will have to pass the value of the foreign key as a parameter of the "create".

    ejbPostCreate is too late, the insert has already happened.



  3. WebSphere and CMRs[ Go to top ]

    My statement above is true for any field defined as "non null" in the DB schema, not just foreign keys.

    Any required field will have to be initialized in ejbCreate.

    Even if your server has some sort of delayed insert feature, it will probably not allow you to control the order of the inserts, and without a well-defined order of inserts you will have the same problem.

    By initializing fields in ejbCreate, and by your code controlling the order of the Home.create() calls, you can explicitly control the order of the inserts.

  4. WebSphere and CMRs[ Go to top ]

    For this thing to work, you'll have to create a sessiom bean that will manage the inserts. In this session bean, you'll have to create a method that calls the create statements of both entity beans.

    Example:

    public class ProfileFacade implements SessionBean {

    public void createRecord(...,...){

    ProfileHome pHome = (ProfileHome) iCtx.lookup(...);
    ProfileSpecificHome psHome = (ProfileSpecificHome) iCtx.lookup(...);
    Profile profile = pHome.create(...);
    ProfileSpecific = psHome.create(profile.getProfileId(), ...);

    }

    }

    The session bean takes care of the transaction. If either inserts fail then the tx will be rolled back.

    Or if you're directly accessing both entity beans from your clients (meaning your using remote entity beans). Although this is highly discouraged, you can use this:

    publi class AnyClass {

    public void anyMethod(...){

    UserTransaction tx = (UserTransaction)
    iCtx.lookup("javax.transaction.UserTransaction");
    try {
    tx.begin
    ProfileHome pHome = (ProfileHome)
    PortableRemoteObject.narrow(iCtx.lookup(...), ...);
    ProfileSpecificHome psHome = (ProfileSpecificHome)
    PortableRemoteObject.narrow(iCtx.lookup(...), ...);
    Profile profile = pHome.create(...);
    ProfileSpecific = psHome.create(profile.getProfileId(), ...);
    tx.commit();
    }
    catch (Exception ex){
    tx.rollback();
    }

    }

    }

    Regards,

    Marco
  5. This has an addendum:

    For this thing to work, you'll have to create a sessiom bean that will manage the inserts. In this session bean, you'll have to create a method that calls the create statements of both entity beans.

    Example:

    public class ProfileFacade implements SessionBean {

       public void createRecord(...,...){

          ProfileHome pHome = (ProfileHome) iCtx.lookup(...);
          ProfileSpecificHome psHome = (ProfileSpecificHome) iCtx.lookup(...);
          Profile profile = pHome.create(...);
          ProfileSpecific = psHome.create(profile, ...); <-- changed

       }

    }

    The session bean takes care of the transaction. If either inserts fail then the tx will be rolled back.

    Or if you're directly accessing both entity beans from your clients (meaning your using remote entity beans). Although this is highly discouraged, you can use this:

    public class AnyClass {

       public void anyMethod(...){

          UserTransaction tx = (UserTransaction)
          iCtx.lookup("javax.transaction.UserTransaction");
          try {
             tx.begin
             ProfileHome pHome = (ProfileHome) PortableRemoteObject.narrow(iCtx.lookup(...), ...);
             ProfileSpecificHome psHome = (ProfileSpecificHome) PortableRemoteObject.narrow(iCtx.lookup(...), ...);
             Profile profile = pHome.create(...);
             ProfileSpecific = psHome.create(profile, ...); <-- changed
             tx.commit();
          }
             catch (Exception ex){
             tx.rollback();
          }

       }

    }

    And in your ProfileSpecificBean, you'll have:

    public class ProfileSpecificBean implements EntityBean {

       public ProfileSpecificPK ejbCreate(Profile profile, ...){
          // initialize none CMR fields
       }

       public void ejbPostCreate(Profile profile, ...){
          setProfile(profile);
          // and other CMR fields...
       }

    }


    Regards,

    Marco

  6. Thanks for the replies. Marco, your final posting (with the changes) is exactly how I was trying to do it. It fails with the NOT NULL exception because when the insert is done during the ejbCreate call, the foreign key field (profile_id) is not set yet. It gets set when the setProfile call is made in the ejbPostCreate. This is obviously too late. I do already have a session bean that calls the create methods and handles the transactions as you mentioned.

    Is is possible to have a CMP field also be a CMR. So could I have profileId as an attribute and the bean referenced by the profileId as a container managed relationship. That way, I could call individual creates by passing in the profileId which would correctly insert and set up the foreign key relationships. And then I could use the container managed relationships to handle the finds and updates. I can't tell if that is allowed or not..

    Thanks,
    Aimee
  7. Yup, it's allowed. In fact, I just tried doing it and my EJBs compiled and deployed properly. If you've got more questions, email me.

    Regards,

    Marco
    mingco@ccsc.com
  8. Is there any reason why you don't want to do this in ejbCreate?

    The only way to have the DB enforce non-null fields and still use CMP, is to pass in the values as part of create. This is true for "mandatory" relationships also (which just become CM fields).

  9. Application servers are not allowed to set the cmr-fields during ejbCreate. If you try this, your code will compile and deploy properly. However, when you run it, you'll get an IllegalStateException (The setXXX method of a cmr-field may not be called during ejbCreate yadah yadah yahah). You may want to consult the EJB Specification Section 10.5.2 bullet #5. The explanation is that you have to have the component interface of your bean (that is, the EJBLocalObject) before you can set the cmr-field. And this becomes available only at ejbPostCreate, not during ejbCreate.
  10. The following will also work. Instead of creating the ProfileSpecifics outside, (in the clients), you can also do it like this. Only catch is, I don't know if this would work with other app servers because in Weblogic you have to specify the "delay-database-insert-until" in your weblogic-cmp-rdbms-jar.xml file for the ProfileBean so the PROFILE_ID will be visible in the database when the ProfileSpecifics are inserted.

    <code>
    abstract public class ProfileBean implements EntityBean {
        public java.math.BigDecimal ejbCreate(java.math.BigDecimal profileId, String version, List profileSpecifics) throws CreateException {
            setProfileId(profileId);
            setVersion(version);
            return null;
        }
        public void ejbPostCreate(java.math.BigDecimal profileId, String version, List profileSpecifics) throws CreateException {
            try {
                Context ctx = new InitialContext();
                ProfileSpecificHome psHome = (ProfileSpecificHome) ctx.lookup("ProfileSpecific");
                for (Iterator i = profileSpecifics.iterator(); i.hasNext(); ){
                    Map psData = (Map) i.next();
                    psHome.create((Profile)entityContext.getEJBLocalObject(), (BigDecimal)psData.get("profileSpecId"),
                                  (String)psData.get("version"), (String)psData.get("name"));
                }
            }
            catch (NamingException ne){
                throw new EJBException(ne);
            }
        }
        public abstract void setProfileId(java.math.BigDecimal profileId);
        public abstract void setVersion(java.lang.String version);
        public abstract void setProfileSpecifics(java.util.Collection profileSpecifics);
        public abstract java.math.BigDecimal getProfileId();
        public abstract java.lang.String getVersion();
        public abstract java.util.Collection getProfileSpecifics();
        // all other EntityBean methods goes here
    }

    abstract public class ProfileSpecificBean implements EntityBean {
        public java.math.BigDecimal ejbCreate(Profile profile, BigDecimal profileSpecId, String version, String name) throws CreateException {
            setProfileSpecId(profileSpecId);
            setVersion(version);
            setName(name);
            return null;
        }
        public void ejbPostCreate(Profile profile, BigDecimal profileSpecId, String version, String name) throws CreateException {
            setProfile(profile);
        }
        public abstract void setProfileSpecId(java.math.BigDecimal profileSpecId);
        public abstract void setProfileId(java.math.BigDecimal profileId);
        public abstract void setVersion(java.lang.String version);
        public abstract void setName(java.lang.String name);
        public abstract void setProfile(profile.Profile profile);
        public abstract java.math.BigDecimal getProfileSpecId();
        public abstract java.math.BigDecimal getProfileId();
        public abstract java.lang.String getVersion();
        public abstract java.lang.String getName();
        public abstract profile.Profile getProfile();
        // all other EntityBean methods goes here
    }

    public class ProfileFacadeBean implements SessionBean {
        public void createProfile(Map profileData, List profileSpecifics) {
            try {
                Context ctx = new InitialContext();
                ProfileHome pHome = (ProfileHome) ctx.lookup("Profile");
                pHome.create((BigDecimal)profileData.get("profileId"), (String)profileData.get("version"), profileSpecifics);
            }
            catch (NamingException ne){
                throw new EJBException(ne);
            }
            catch (CreateException ce){
                throw new EJBException(ce);
            }
        }
        public String getProfileAsString(BigDecimal profileId) {
            StringBuffer sBuf = new StringBuffer();
            try {
                int ctr = 0;
                Context ctx = new InitialContext();
                ProfileHome pHome = (ProfileHome) ctx.lookup("Profile");
                Profile profile = pHome.findByPrimaryKey(profileId);
                sBuf.append("====================================================================================\n");
                sBuf.append("Profile:\n");
                sBuf.append("\tID: ");
                sBuf.append(profile.getProfileId());
                sBuf.append("\n\tVersion: ");
                sBuf.append(profile.getVersion());
                sBuf.append("\n");
                sBuf.append("====================================================================================\n");
                for (Iterator i = profile.getProfileSpecifics().iterator(); i.hasNext(); ){
                    sBuf.append("\tProfile Specific # ");
                    sBuf.append(++ctr);
                    ProfileSpecific ps = (ProfileSpecific) i.next();
                    sBuf.append("\n\t\tID: ");
                    sBuf.append(ps.getProfileSpecId());
                    sBuf.append("\n\t\tVersion: ");
                    sBuf.append(ps.getVersion());
                    sBuf.append("\n\t\tName: ");
                    sBuf.append(ps.getName());
                    sBuf.append("\n ===============================================================================\n");
                }
                sBuf.append("====================================================================================\n");
            }
            catch (NamingException ne){
                throw new EJBException(ne);
            }
            catch (FinderException fe){
                throw new EJBException(fe);
            }
            return sBuf.toString();
        }
        // all other SessionBean methods goes here
    }

    public class ProfileTest {

        public static Hashtable env = new Hashtable();
        static {
            env.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory");
            env.put(Context.PROVIDER_URL, "t3://localhost:9105");
        }

        public static void main(String[] args) throws Exception {
            BigDecimal one = new BigDecimal(1);
            BigDecimal pID = new BigDecimal(212);
            Map pData = new HashMap();
            pData.put("profileId", pID);
            pData.put("version", "v.1.6");

            List psData = new ArrayList(10);
            BigDecimal id = new BigDecimal(1000).multiply(pID);
            for (int i = 0; i < 10; i++){
                id = id.add(one);
                Map data = new HashMap();
                data.put("profileSpecId", id);
                data.put("version", pData.get("version") + "." + i);
                data.put("name", "Project #" + i);
                psData.add(data);
            }

            Context ctx = new InitialContext(env);
            ProfileFacadeHome fHome = (ProfileFacadeHome) PortableRemoteObject.narrow(
                    ctx.lookup("ProfileFacade"), ProfileFacadeHome.class);

            ProfileFacade facade = fHome.create();
            facade.createProfile(pData, psData);

            System.out.println(facade.getProfileAsString(pID));
        }
    }
    </code>