ibatis-user-java mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Dan Forward <dan-nab...@forwardhome.com>
Subject Re: Mapping a Complex Object
Date Sun, 03 Jan 2010 14:56:48 GMT


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


Mime
View raw message