Discussions

EJB design: Should value objects overwrite equals(...)?

  1. Should value objects overwrite equals(...)? (9 messages)

    This question is easy and I hope that someone can reply to it.

    Should value objects (for example ArticleVO) overwrite the equals(...) method?

    If yes, on what should the comparison be based? On the primary key, on all attribute values or only on all attribute values except the primary key?
  2. Primary key must be the criteria for matching the VO. Since even all the attributes are matching, if primary keys are different, vo's are different. Also some times it depends on the type of requirement...under hand.
  3. What if we use a Entity Key as primary key for all VOs? This is only a technical key. Should this Entity Key be used or the real primary key (which would be article no perhaps)?
  4. Hi Hans,
    To paraphrase Joshua Bloch from "Effective Java":
    In general any programmer who compares references to value objects using equals() would expect to find out whether they are "logically equivalent", not whether they refer to the same object. This makes overriding equals() necessary.

    He notes typesafe enum classes as an exception, but I haven't used them, so I can't comment about them.

    Chapter 3 of his book is available online and provides a very good discussion about the issues involved in overriding equals():
    http://developer.java.sun.com/developer/Books/effectivejava/Chapter3.pdf

    The chapter also provides answers to the second part of your question. To provide a quick answer: the comparisons must be based on first on fields that are more likely to differ, or less expensive to compare, or both. If the semantics of your value object include the notion of a primary key, a comparison involving these fields (least expensive first) would suffice. A composite value object may not require or have a primary key, in which case you may need to compare all the fields, but you can do that in a least-expensive-first manner.

    hope that helps
    George
  5. Okay, but in the case of ArticleVO should the technical Entity Key be used or the article no.?

    I mean with Entity Key a primary key we use for all VOs. Every VO has an Entity Key. Every EK is unique in the whole database. But this is only a technical entity key for our ERP software system based on EJB.

    So what to use: the EK or the "real" primary key, for example article no.?
  6. Hi Hans,
    I believe keeping a VO disjoint from Entity Bean implementation details is good practice. An Entity Bean PK is one such implementation detail. The "technical Entity Key" you refer to is a souped-up wrapper for a field or a set of fields (in which case you have a PK class). Using the PK class in your VO may not be recommended. Since (I assume) your VO is a subset of the fields of the Entity Bean, you can use the same fields that comprise the "Entity Key" to uniquely identify the VO.

    Hope I understood you correctly.
    regards
    George
  7. Hi George,

    if I understand you right, you mean that the equals(...) method should be in the PK class and not in the VO class.

    We use a framework in which the VO classes extend an AbstractValueObject class. The AbstractValueObject class contains the PK (which is only EntityKey and Version variables). I assume you think that this is a bad design.

    Where would you place the equals method? In the AbstractValueObject comparing only EntityKey and/or Version? Or in the VO?

    In the case of an ArticleVO, how would a equals(...) method work? Maybe you can give me some concrete information.
  8. Hi Hans,
    I did not mean that "the equals(...) method should be in the PK class and not in the VO class". Sorry if I conveyed that idea. Let me explain:)
    Let's take the example of an Entity Bean called CustCars with a composite primary key (customerID, carID) (used in some application that tracks customers and all the cars they purchased, in a domain where customers frequently purchase more than one car). The bean now has a PK class called, say, CustCarsPK, with two fields customerID and carID. The Value Object for this entity bean would have two fields as well: customerID and carID. If I understand you correctly, you are using tha single field of type CustCarsPK in the ValueObject. I don't see the benefit of doing this -- In fact, this exposes implementation details to the user of the Value Object, who could be a client-side business delegate class communicating with a session facade.

    Using an AbstractValueObject is a great way to achieve reusability, especially for implementing toString() methods for value objects. See
    http://www.hpmiddleware.com/newsletters/webservicesnews/features/ for more details.

    To answer your question: the equals() method is best placed in the VO. The implementation would compare the fields individually. As I noted above, there is no advantage/merit to using the PK class directly in the value object.
    As for more concrete information on implementing the equals() method, here's a link again to the sample chapter from Joshua Bloch's book:
    http://developer.java.sun.com/developer/Books/effectivejava/Chapter3.pdf

    hope that helps.
    regards
    George
  9. Great! And thank you for that interesting link!

    Just one question (to you or whoever wants to reply): You wrote: "If I understand you correctly, you are using tha single field of type CustCarsPK in the ValueObject".

    No, we don't do this. We simply have no PK class at all. We have only a VO class. The PK class is replaced by a attribute of type long called "entityKey". Every VO has such a attribute. The origin of the attribute is in the AbstractValueObject class. Besides this every VO has a integer attibute called version which increments when the VO is updated in the database. The origin of that version attribute is in the AbstractValueObject class too.

    As I said we use that framework which comes with this design. Do you think it is bad to have a "technical" entityKey? It already started to make problems when migrating data from the old Informix database (with a completely different organisation) to the new Oracle databese scheme. And if we make test data it sometimes causes problems too. Besides this it is a strange concept I think (entityKey).

    Maybe you or someone else can tell me what he thinks about that entityKey instead a PK class?
  10. As long as the entity key uniquely identifies the value object, I don't see a problem.

    The design I use is a VO which extends a MutableVO and a Key which extends Key or CacheableKey. My mutableVO overrides equals and calls getKey().equals() so I don't have to do it in all my VO's. My Keys override the equals(), and hashCode() methods. I just use some IntelliJ plugins to autogenerate the Getters/Setters, toString() methods. I wrote some plugins to generate my PKEquals, PKHashCode, and PKValidate methods for my keys so I don't have to retype all the logic a million times. All I actually write are the 2 constructors and the variables. Here's an example.

    public final class CardPK extends CacheableKey implements Serializable
    {
        Integer bin;
        String cardNum;

        public CardPK()
        {
            setControllerClass("com.tab.core.server.dao.CardDAO");
        }

        public CardPK(final Integer bin, final String cardNum)
        {
            this();
            this.bin = bin;
            this.cardNum = cardNum;
        }

        public Integer getBin()
        {
            return bin;
        }

        public void setBin(final Integer bin)
        {
            this.bin = bin;
        }

        public String getCardNum()
        {
            return cardNum;
        }

        public void setCardNum(final String cardNum)
        {
            this.cardNum = cardNum;
        }

        public String toString()
        {
            final StringBuffer buffy = new StringBuffer("com.tab.core.shared.vo.CardPK {");

            buffy.append("bin=");
            buffy.append(bin);
            buffy.append(", ");

            if (cardNum == null)
            {
                buffy.append("cardNum=null");
            }
            else
            {
                buffy.append("cardNum='");
                buffy.append(cardNum);
                buffy.append('\'');
            }
            buffy.append("}");

            return buffy.toString();
        }

        public boolean equals(final Object obj)
        {
            CardPK cast;

            if (obj == null)
                return false;
            if (obj == this)
                return true;

            if (!(obj instanceof CardPK))
            {
                if (obj instanceof MutableVO)
                    cast = (CardPK) ((MutableVO) obj).getKey();
                else
                    return false;
            }
            else
                cast = (CardPK) obj;

            if (bin == null && cast.bin == null)
                return true;
            if (bin == null || cast.bin == null)
                return false;
            if (!(bin.equals(cast.bin)))
                return false;
            if (cardNum == null && cast.cardNum == null)
                return true;
            if (cardNum == null || cast.cardNum == null)
                return false;
            if (!(cardNum.trim().equals(cast.cardNum.trim())))
                return false;
            return true;
        }

        public int hashCode()
        {
            int _hashCode = 0;
            if (validate())
            {
                _hashCode = 29 * _hashCode + bin.hashCode();
                _hashCode = 29 * _hashCode + cardNum.trim().hashCode();
            }
            return _hashCode;
        }

        public boolean validate()
        {
            if (bin == null)
                return false;
            else if (StringUtil.nullOrEmpty(cardNum))
                return false;
            else
                return true;
        }
    }