Return-Path: Delivered-To: apmail-ibatis-user-java-archive@www.apache.org Received: (qmail 13205 invoked from network); 3 Jan 2010 14:57:19 -0000 Received: from hermes.apache.org (HELO mail.apache.org) (140.211.11.3) by minotaur.apache.org with SMTP; 3 Jan 2010 14:57:19 -0000 Received: (qmail 70049 invoked by uid 500); 3 Jan 2010 14:57:19 -0000 Delivered-To: apmail-ibatis-user-java-archive@ibatis.apache.org Received: (qmail 70002 invoked by uid 500); 3 Jan 2010 14:57:19 -0000 Mailing-List: contact user-java-help@ibatis.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: user-java@ibatis.apache.org Delivered-To: mailing list user-java@ibatis.apache.org Received: (qmail 69994 invoked by uid 99); 3 Jan 2010 14:57:19 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 03 Jan 2010 14:57:19 +0000 X-ASF-Spam-Status: No, hits=-0.0 required=10.0 tests=SPF_HELO_PASS,SPF_PASS X-Spam-Check-By: apache.org Received-SPF: pass (nike.apache.org: domain of lists@nabble.com designates 216.139.236.158 as permitted sender) Received: from [216.139.236.158] (HELO kuber.nabble.com) (216.139.236.158) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 03 Jan 2010 14:57:09 +0000 Received: from isper.nabble.com ([192.168.236.156]) by kuber.nabble.com with esmtp (Exim 4.63) (envelope-from ) id 1NRRsu-0003Gg-8W for user-java@ibatis.apache.org; Sun, 03 Jan 2010 06:56:48 -0800 Message-ID: <27002148.post@talk.nabble.com> Date: Sun, 3 Jan 2010 06:56:48 -0800 (PST) From: Dan Forward To: user-java@ibatis.apache.org Subject: Re: Mapping a Complex Object In-Reply-To: <4B4021CF.7040200@burntmail.com> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Nabble-From: dan-nabble@forwardhome.com References: <26961927.post@talk.nabble.com> <4B3ACB48.6000303@burntmail.com> <26970785.post@talk.nabble.com> <4B3C3F62.1040506@burntmail.com> <26997280.post@talk.nabble.com> <4B4021CF.7040200@burntmail.com> X-Virus-Checked: Checked by ClamAV on apache.org Guy Rouillier-2 wrote: > > Comments inline. Overall, you seem to have made this much more > complicated than it needs to be. Looking at your database schema from > your original message, all the table columns are simple strings or > number, except for the gender enum. But you've elected to make every > column a distinct object type. That is why your solution is more > complex than you might wish. > > Because you've elected to transform every column into a distinct object > type, you have to tell iBATIS what those object types are. It can't > possibly guess at such things. And that is why you are having to > specify all the javaType's. If you would have used simple strings and > numbers, you would not have to do so. > Thank you for your forthright comments, Guy. I know I could have used Strings and ints, but there is something to be said for type safety and compile-time checking of parameters. Here is the method signature of the sole constructor I am using: private User(UserID id, Name name, Gender gender, EmailAddress email, TelephoneNumber phone, LocalDate birthDate, SHA1 passwordHash, StaticFileID avatarID, OrganizationID organizationID, int version) It is called by this static factory method: public static User getInstance(UserID id, Gender gender, EmailAddress email, TelephoneNumber phone, LocalDate birthDate, SHA1 passwordHash, StaticFileID avatarID, OrganizationID organizationID, Integer version) { return new User(id, null, gender, email, phone, birthDate, passwordHash, avatarID, organizationID, version); } Had I used a collection of Strings and ints, it would be easy to accidentally swap the order of the email address and phone number when calling the constructor and the compiler would not say a word. With a strongly-typed approach, I know I have a valid email address and a valid phone number when this constructor is called. The domain layer then becomes very safe and easy to work with. However, it does appear to complicate the persistence layer. When describing the javaType attribute of the constructor element on page 32, the manual states, "iBATIS can usually figure out the type if you're mapping to a JavaBean." I think this was an unfortunate copy and paste from the previous section, because, as you indicated, it really has little idea what the type is in the context of the constructor. It could make a good guess by comparing column names to the corresponding getters, but it would only be a guess. [snip] > >> I was surprised that I had to specify the javaType for every parameter. >> Otherwise, iBATIS treated everything as an Object and could not find a >> corresponding constructor. I then discovered that iBATIS was looking for >> an >> Integer argument for the version even though I specified the javaType to >> be >> an int. Finally, I had to remove name from the constructor since >> constructor >> tags do not support child association tags. > > Already discussed the need for all the javaType's. I don't know about > the int; I've used them successfully without issue. I don't understand > what you mean by "I had to remove name from the constructor"; I don't > see a column called "name" in your table. > > You can now see how the constructor takes a Name object. A Name is constructed from the first_name, middle_name, last_name, and suffix columns in the table. (This is why I had to use the association element in the mapper configuration.) The constructor element can only have idArg and arg children, not association elements, so the Name has to be added with setName(name). I see no way to have iBATIS inject the associated Name into the constructor. When I added a version column to implement optimistic locking, my unit test failed. The exception said my ObjectFactory could not find a matching constructor (static factory). The exception listed the types it was checking for, so I saw that it was trying to use Integer for the version, not int. When I changed my static factory to use an Integer (and with no other changes to the class or the Mapper), it worked again. The class still uses an int internally and the getter returns an int, but the static factory requires an Integer just for iBATIS. This is strange because the example on page 32 of the manual shows a constructor that takes an int and a String. > >> As a side note, I try to follow the recommendation by Joshua Bloch in >> Effective Java to use static factory methods instead of constructors, so >> I >> only have private constructors. I used DefaultObjectFactory as a model to >> create my own ObjectFactory that first looks for a matching static >> factory >> method before looking for a constructor. > > I just use JavaBeans instead of trying to do all the data filling from > the constructor. iBATIS will use the empty constructor automatically. > Any particular reason you want to do the data assignments via > constructors? > > I like to use immutable objects where possible (which is another recommendation from Effective Java), so I only write setters for properties that can change. For example, the ID of the object should never change, so there is no setter. I admit, if I had used JavaBeans with empty constructors and getters and setters for every property, iBATIS would be a breeze, but I believe that is a bad practice in the domain layer. I know iBATIS can use private setters, but the very idea of an external class even knowing about private fields and methods of another class goes contrary to the principle of encapsulation. I want to avoid it. It all boils down to why should I have to change my chosen domain model to suit the whims of my persistence layer? Why not have the persistence layer be accommodating of several well-known domain approaches? Why not, as a catch-all, have the user implement some method of this form:? public MyObject getInstance(ResultSet rs); iBATIS could call it, cache the result, and behave normally without the need a ResultMap. If that was too risky, the ResultSet could be replaced with a HashMap. These are rhetorical questions, not practical ones. The fact remains that I have a project that needs to persist objects and so I have to jump through hoops to get there with iBATIS or write my own persistence layer if I wish to be true to best practices in the domain layer. At first I attempted the Hibernate route, but believe me, there were many more hoops to jump through and there seemed to be too much black magic going on, so that when things failed, it was a monumental effort to discover why. > >> One of the reasons I chose iBATIS was that Hibernate put too many >> constraints on my domain model. It isn't really a POJO if you say it has >> to >> have a public constructor, an empty constructor, and setters for every >> property. iBATIS is less strict, but still has some hoops to jump >> through. >> Wouldn't it be nice to have a persistence layer that transparently >> accommodated the domain model? What if I wanted to use a separate Factory >> class to create my User objects? > > Look at page 14 in the documentation. You can supply your own > ObjectFactory. > > In fact I am supplying my own ObjectFactory. It is just a copy and paste job from DefaultObjectFactory that first checks for static factory methods on the class, so it is for general use. It looks like there can be only one ObjectFactory per iBATIS configuration. I suppose I could inspect the type being passed in and call custom factories for every difficult class. If I could only get the Name class to be included in the "constructor," this would be a very appealing approach. Once again, thank you for your thoughtful replies. They have been a tremendous help in getting to this point. Sincerely, Dan Forward -- View this message in context: http://old.nabble.com/Mapping-a-Complex-Object-tp26961927p27002148.html Sent from the iBATIS - User - Java mailing list archive at Nabble.com. --------------------------------------------------------------------- To unsubscribe, e-mail: user-java-unsubscribe@ibatis.apache.org For additional commands, e-mail: user-java-help@ibatis.apache.org