扫扫关注“茶爸爸”微信公众号
坚持最初的执着,从不曾有半点懈怠,为优秀而努力,为证明自己而活。
Mapper XML Files
The true power of MyBatis is in the Mapped Statements. This is where the magic happens. For all of their power,the Mapper XML files are relatively simple. Certainly if you were to compare them to the equivalent JDBC code,you would immediately see a savings of 95% of the code. MyBatis was built to focus on the sql,and does its best to stay out of your way.
The Mapper XML files have only a few first class elements (in the order that they should be defined):
- cache– Configuration of the cache for a given namespace.
- cache-ref– Reference to a cache configuration from another namespace.
- resultMap– The most complicated and powerful element that describes how to load your objects from the database result sets.
parameterMap– Deprecated! Old-school way to map parameters. Inline parameters are preferred and this element may be removed in the future. Not documented here.- sql– A reusable chunk of sql that can be referenced by other statements.
- insert– A mapped INSERT statement.
- update– A mapped UPDATE statement.
- delete– A mapped DELETE statement.
- select– A mapped SELECT statement.
The next sections will describe each of these elements in detail,starting with the statements themselves.
select
The select statement is one of the most popular elements that you'll use in MyBatis. Putting data in a database isn't terribly valuable until you get it back out,so most applications query far more than they modify the data. For every insert,update or delete,there is probably many selects. This is one of the founding principles of MyBatis,and is the reason so much focus and effort was placed on querying and result mapping. The select element is quite simple for simple cases. For example:
<select id="selectPerson" parameterType="int" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
This statement is called selectPerson,takes a parameter of type int (or Integer),and returns a HashMap keyed by column names mapped to row values.
Notice the parameter notation:
#{id}
This tells MyBatis to create a PreparedStatement parameter. With JDBC,such a parameter would be identified by a "?" in sql passed to a new PreparedStatement,something like this:
// Similar JDBC code,NOT MyBatis… String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(selectPerson); ps.setInt(1,id);
Of course,there's a lot more code required by JDBC alone to extract the results and map them to an instance of an object,which is what MyBatis saves you from having to do. There's a lot more to know about parameter and result mapping. Those details warrant their own section,which follows later in this section.
The select element has more attributes that allow you to configure the details of how each statement should behave.
<select id="selectPerson" parameterType="int" parameterMap="deprecated" resultType="hashmap" resultMap="personResultMap" flushCache="false" useCache="true" timeout="10000" fetchSize="256" statementType="PREPARED" resultSetType="FORWARD_ONLY">
Attribute | Description | |
---|---|---|
id | A unique identifier in this namespace that can be used to reference this statement. | |
parameterType | The fully qualified class name or alias for the parameter that will be passed into this statement. This attribute is optional because MyBatis can calculate the TypeHandler to use out of the actual parameter passed to the statement. Default isunset. | |
|
||
resultType | The fully qualified class name or alias for the expected type that will be returned from this statement. Note that in the case of collections,this should be the type that the collection contains,not the type of the collection itself. UseresultTypeORresultMap,not both. | |
resultMap | A named reference to an externalresultMap. Result maps are the most powerful feature of MyBatis,and with a good understanding of them,many difficult mapping cases can be solved. UseresultMapORresultType,not both. | |
flushCache | Setting this to true will cause the local and 2nd level caches to be flushed whenever this statement is called. Default:falsefor select statements. | |
useCache | Setting this to true will cause the results of this statement to be cached in 2nd level cache. Default:truefor select statements. | |
timeout | This sets the number of seconds the driver will wait for the database to return from a request,before throwing an exception. Default isunset(driver dependent). | |
fetchSize | This is a driver hint that will attempt to cause the driver to return results in batches of rows numbering in size equal to this setting. Default isunset(driver dependent). | |
statementType | Any one ofSTATEMENT,PREPAREDorCALLABLE. This causes MyBatis to useStatement,204)">PreparedStatementorCallableStatementrespectively. Default:PREPARED. | |
resultSetType | Any one ofFORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE. Default isdatabaseId | In case there is a configured databaseIdProvider,MyBatis will load all statements with nodatabaseIdattribute or with adatabaseIdthat matches the current one. If case the same statement if found with and without thedatabaseIdthe latter will be discarded. |
resultOrdered | This is only applicable for nested result select statements: If this is true,it is assumed that nested results are contained or grouped together such that when a new main result row is returned,no references to a prevIoUs result row will occur anymore. This allows nested results to be filled much more memory friendly. Default:false. | |
resultSets | This is only applicable for multiple result sets. It lists the result sets that will be returned by the statement and gives a name to each one. Names are separated by commas. |
insert,update and delete
The data modification statements insert,update and delete are very similar in their implementation:
<insert id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" keyProperty="" keyColumn="" useGeneratedKeys="" timeout="20"> <update id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20"> <delete id="insertAuthor" parameterType="domain.blog.Author" flushCache="true" statementType="PREPARED" timeout="20">
Attribute | Description |
---|---|
|
|
Setting this to true will cause the 2nd level and local caches to be flushed whenever this statement is called. Default:truefor insert,update and delete statements. | |
This sets the maximum number of seconds the driver will wait for the database to return from a request,204)">useGeneratedKeys | (insert and update only) This tells MyBatis to use the JDBCgetGeneratedKeysmethod to retrieve keys generated internally by the database (e.g. auto increment fields in RDBMS like MysqL or sql Server). Default:false |
keyProperty | (insert and update only) Identifies a property into which MyBatis will set the key value returned bygetGeneratedKeys,or by aselectKeychild element of the insert statement. Default:unset. Can be a comma separated list of property names if multiple generated columns are expected. |
keyColumn | (insert and update only) Sets the name of the column in the table with a generated key. This is only required in certain databases (like Postgresql) when the key column is not the first column in the table. Can be a comma separated list of columns names if multiple generated columns are expected. |
databaseIdthe latter will be discarded. |
The following are some examples of insert,update and delete statements.
<insert id="insertAuthor"> insert into Author (id,username,password,email,bio) values (#{id},#{username},#{password},#{email},#{bio}) </insert> <update id="updateAuthor"> update Author set username = #{username},password = #{password},email = #{email},bio = #{bio} where id = #{id} </update> <delete id="deleteAuthor"> delete from Author where id = #{id} </delete>
As mentioned,insert is a little bit more rich in that it has a few extra attributes and sub-elements that allow it to deal with key generation in a number of ways.
First,if your database supports auto-generated key fields (e.g. MysqL and sql Server),then you can simply setuseGeneratedKeys="true"and set thekeyPropertyto the target property and you're done. For example,if theAuthortable above had used an auto-generated column type for the id,the statement would be modified as follows:
<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id"> insert into Author (username,bio) values (#{username},#{bio}) </insert>
MyBatis has another way to deal with key generation for databases that don't support auto-generated column types,or perhaps don't yet support the JDBC driver support for auto-generated keys.
Here's a simple (silly) example that would generate a random ID (something you'd likely never do,but this demonstrates the flexibility and how MyBatis really doesn't mind):
<insert id="insertAuthor"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select CAST(RANDOM()*1000000 as INTEGER) a from SYSIBM.SYSDUMMY1 </selectKey> insert into Author (id,bio,favourite_section) values (#{id},#{bio},#{favouriteSection,jdbcType=VARCHAR}) </insert>
In the example above,the selectKey statement would be run first,theAuthorid property would be set,and then the insert statement would be called. This gives you a similar behavior to an auto-generated key in your database without complicating your Java code.
The selectKey element is described as follows:
<selectKey keyProperty="id" resultType="int" order="BEFORE" statementType="PREPARED">
Attribute | Description |
---|---|
The target property where the result of theselectKeystatement should be set. Can be a comma separated list of property names if multiple generated columns are expected. | |
The column name(s) in the returned result set that match the properties. Can be a comma separated list of column names if multiple generated columns are expected. | |
The type of the result. MyBatis can usually figure this out,but it doesn't hurt to add it to be sure. MyBatis allows any simple type to be used as the key,including Strings. If you are expecting multiple generated columns,then you can use an Object that contains the expected properties,or a Map. | |
order | This can be set toBEFOREorAFTER. If set toBEFORE,then it will select the key first,set thekeyPropertyand then execute the insert statement. If set toAFTER,it runs the insert statement and then theselectKeystatement – which is common with databases like Oracle that may have embedded sequence calls inside of insert statements. |
Same as above,MyBatis supportsPREPAREDandCALLABLEstatement types that map toPreparedStatementandCallableStatementrespectively. |
sql
This element can be used to define a reusable fragment of sql code that can be included in other statements. For example:
The sql fragment can then be included in another statement,for example:
<select id="selectUsers" resultType="map"> select <include refid="userColumns"/> from some_table where id = #{id} </select>
Parameters
In all of the past statements,you've seen examples of simple parameters. Parameters are very powerful elements in MyBatis. For simple situations,probably 90% of the cases,there's not much too them,245)"><select id="selectUsers" resultType="User"> select id,password from users where id = #{id} </select>
The example above demonstrates a very simple named parameter mapping. The parameterType is set toint,so therefore the parameter could be named anything. Primitive or simply data types such asIntegerandStringhave no relevant properties,and thus will replace the full value of the parameter entirely. However,if you pass in a complex object,then the behavior is a little different. For example:
<insert id="insertUser" parameterType="User"> insert into users (id,password) values (#{id},#{password}) </insert>
If a parameter object of type User was passed into that statement,the id,username and password property would be looked up and their values passed to aPreparedStatementparameter.
That's nice and simple for passing parameters into statements. But there are a lot of other features of parameter maps.
First,like other parts of MyBatis,parameters can specify a more specific data type.
#{property,javaType=int,jdbcType=NUMERIC}
Like the rest of MyBatis,the javaType can almost always be determined from the parameter object,unless that object is aHashMap. Then thejavaTypeshould be specified to ensure the correctTypeHandleris used.
NOTEThe JDBC Type is required by JDBC for all nullable columns,ifnullis passed as a value. You can investigate this yourself by reading the JavaDocs for thePreparedStatement.setNull()method.
To further customize type handling,you can also specify a specificTypeHandlerclass (or alias),245)">#{age,jdbcType=NUMERIC,typeHandler=MyTypeHandler}
So already it seems to be getting verbose,but the truth is that you'll rarely set any of these.
For numeric types there's also anumericScalefor determining how many decimal places are relevant.
#{height,javaType=double,numericScale=2}
Finally,the mode attribute allows you to specifyIN,204)">OUTorINOUTparameters. If a parameter isINOUT,the actual value of the parameter object property will be changed,just as you would expect if you were calling for an output parameter. If themode=OUT(orINOUT) and thejdbcType=CURSOR(i.e. Oracle REFCURSOR),you must specify aresultMapto map theResultSetto the type of the parameter. Note that thejavaTypeattribute is optional here,it will be automatically set toResultSetif left blank with aCURSORas thejdbcType.
#{department,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=departmentResultMap}
MyBatis also supports more advanced data types such as structs,but you must tell the statement the type name when registering the out parameter. For example (again,don't break lines like this in practice):
#{middleInitial,jdbcType=STRUCT,jdbcTypeName=MY_TYPE,resultMap=departmentResultMap}
Despite all of these powerful options,most of the time you'll simply specify the property name,and MyBatis will figure out the rest. At most,you'll specify thejdbcTypefor nullable columns.
#{firstName} #{middleInitial,jdbcType=VARCHAR} #{lastName}
String Substitution
By default,using the#{}Syntax will cause MyBatis to generatePreparedStatementproperties and set the values safely against thePreparedStatementparameters (e.g. ?). While this is safer,faster and almost always preferred,sometimes you just want to directly inject a string unmodified into the sql Statement. For example,for ORDER BY,you might use something like this:
ORDER BY ${columnName}
Here MyBatis won't modify or escape the string.
NOTEIt's not safe to accept input from a user and supply it to a statement unmodified in this way. This leads to potential sql Injection attacks and therefore you should either disallow user input in these fields,or always perform your own escapes and checks.
Result Maps
TheresultMapelement is the most important and powerful element in MyBatis. It's what allows you to do away with 90% of the code that JDBC requires to retrieve data fromResultSets,and in some cases allows you to do things that JDBC does not even support. In fact,to write the equivalent code for something like a join mapping for a complex statement could probably span thousands of lines of code. The design of theResultMaps is such that simple statements don't require explicit result mappings at all,and more complex statements require no more than is absolutely necessary to describe the relationships.
You've already seen examples of simple mapped statements that don't have an explicitresultMap. For example:
<select id="selectUsers" resultType="map"> select id,hashedPassword from some_table where id = #{id} </select>
Such a statement simply results in all columns being automatically mapped to the keys of aHashMap,as specified by theresultTypeattribute. While useful in many cases,aHashMapdoesn't make a very good domain model. It's more likely that your application will use JavaBeans or POJOs (Plain Old Java Objects) for the domain model. MyBatis supports both. Consider the following JavaBean:
package com.someapp.model; public class User { private int id; private String username; private String hashedPassword; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getHashedPassword() { return hashedPassword; } public void setHashedPassword(String hashedPassword) { this.hashedPassword = hashedPassword; } }
Based on the JavaBeans specification,the above class has 3 properties: id,and hashedPassword. These match up exactly with the column names in the select statement.
Such a JavaBean could be mapped to aResultSetjust as easily as theHashMap.
<select id="selectUsers" resultType="com.someapp.model.User"> select id,hashedPassword from some_table where id = #{id} </select>
And remember that TypeAliases are your friend. Use them so that you don't have to keep typing the fully qualified path of your class out. For example:
<!-- In Config XML file --> <typeAlias type="com.someapp.model.User" alias="User"/> <!-- In sql Mapping XML file --> <select id="selectUsers" resultType="User"> select id,hashedPassword from some_table where id = #{id} </select>
In these cases MyBatis is automatically creating aResultMapbehind the scenes to auto-map the columns to the JavaBean properties based on name. If the column names did not match exactly,you could employ select clause aliases (a standard sql feature) on the column names to make the labels match. For example:
<select id="selectUsers" resultType="User"> select user_id as "id",user_name as "userName",hashed_password as "hashedPassword" from some_table where id = #{id} </select>
The great thing aboutResultMaps is that you've already learned a lot about them,but you haven't even seen one yet! These simple cases don't require any more than you've seen here. Just for example sake,let's see what this last example would look like as an external
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
And the statement that references it uses theresultMapattribute to do so (notice we removed theresultTypeattribute). For example:
<select id="selectUsers" resultMap="userResultMap"> select user_id,user_name,hashed_password from some_table where id = #{id} </select>
Now if only the world were always that simple.
Advanced Result Maps
MyBatis was created with one idea in mind: Databases aren't always what you want or need them to be. While we'd love every database to be perfect 3rd normal form or BCNF,they aren't. And it would be great if it was possible to have a single database map perfectly to all of the applications that use it,it's not. Result Maps are the answer that MyBatis provides to this problem.
For example,how would we map this statement?
<!-- Very Complex Statement --> <select id="selectBlogDetails" resultMap="detailedBlogResultMap"> select B.id as blog_id,B.title as blog_title,B.author_id as blog_author_id,A.id as author_id,A.username as author_username,A.password as author_password,A.email as author_email,A.bio as author_bio,A.favourite_section as author_favourite_section,P.id as post_id,P.blog_id as post_blog_id,P.author_id as post_author_id,P.created_on as post_created_on,P.section as post_section,P.subject as post_subject,P.draft as draft,P.body as post_body,C.id as comment_id,C.post_id as comment_post_id,C.name as comment_name,C.comment as comment_text,T.id as tag_id,T.name as tag_name from Blog B left outer join Author A on B.author_id = A.id left outer join Post P on B.id = P.blog_id left outer join Comment C on P.id = C.post_id left outer join Post_Tag PT on PT.post_id = P.id left outer join Tag T on PT.tag_id = T.id where B.id = #{id} </select>
You'd probably want to map it to an intelligent object model consisting of a Blog that was written by an Author,and has many Posts,each of which may have zero or many Comments and Tags. The following is a complete example of a complex ResultMap (assume Author,Blog,Post,Comments and Tags are all type aliases). Have a look at it,but don't worry,we're going to go through each step. While it may look daunting at first,it's actually very simple.
<!-- Very Complex Result Map --> <resultMap id="detailedBlogResultMap" type="Blog"> <constructor> <idArg column="blog_id" javaType="int"/> </constructor> <result property="title" column="blog_title"/> <association property="author" javaType="Author"> <id property="id" column="author_id"/> <result property="username" column="author_username"/> <result property="password" column="author_password"/> <result property="email" column="author_email"/> <result property="bio" column="author_bio"/> <result property="favouriteSection" column="author_favourite_section"/> </association> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <association property="author" javaType="Author"/> <collection property="comments" ofType="Comment"> <id property="id" column="comment_id"/> </collection> <collection property="tags" ofType="Tag" > <id property="id" column="tag_id"/> </collection> <discriminator javaType="int" column="draft"> <case value="1" resultType="DraftPost"/> </discriminator> </collection> </resultMap>
TheresultMapelement has a number of sub-elements and a structure worthy of some discussion. The following is a conceptual view of theresultMapelement.
resultMap
- constructor- used for injecting results into the constructor of a class upon instantiation
- idArg- ID argument; flagging results as ID will help improve overall performance
- arg- a normal result injected into the constructor
- id– an ID result; flagging results as ID will help improve overall performance
- result– a normal result injected into a field or JavaBean property
- association– a complex type association; many results will roll up into this type
- nested result mappings – associations areresultMaps themselves,or can refer to one
- collection– a collection of complex types
- nested result mappings – collections arediscriminator– uses a result value to determine whichresultMapto use
- case– a case is a result map based on some value
- nested result mappings – a case is also a result map itself,and thus can contain many of these same elements,or it can refer to an external resultMap.
- case– a case is a result map based on some value
ResultMap Attributes Attribute Description id A unique identifier in this namespace that can be used to reference this result map. type A fully qualified Java class name,or a type alias (see the table above for the list of built-in type aliases). autoMapping If present,MyBatis will enable or disable the automapping for this ResultMap. This attribute overrides the global autoMappingBehavior. Default: unset. Best PracticeAlways build ResultMaps incrementally. Unit tests really help out here. If you try to build a giganticresultMaplike the one above all at once,it's likely you'll get it wrong and it will be hard to work with. Start simple,and evolve it a step at a time. And unit test! The downside to using frameworks is that they are sometimes a bit of a black Box (open source or not). Your best bet to ensure that you're achieving the behavIoUr that you intend,is to write unit tests. It also helps to have them when submitting bugs.
The next sections will walk through each of the elements in more detail.
- nested result mappings – collections arediscriminator– uses a result value to determine whichresultMapto use
id & result
<id property="id" column="post_id"/> <result property="subject" column="post_subject"/>
These are the most basic of result mappings. Bothid,andresultmap a single column value to a single property or field of a simple data type (String,int,double,Date,etc.).
The only difference between the two is thatidwill flag the result as an identifier property to be used when comparing object instances. This helps to improve general performance,but especially performance of caching and nested result mapping (i.e. join mapping).
Each has a number of attributes:
Attribute | Description |
---|---|
property | The field or property to map the column result to. If a matching JavaBeans property exists for the given name,then that will be used. Otherwise,MyBatis will look for a field of the given name. In both cases you can use complex property navigation using the usual dot notation. For example,you can map to something simple like:username,or to something more complicated like:address.street.number. |
column | The column name from the database,or the aliased column label. This is the same string that would normally be passed toresultSet.getString(columnName). |
javaType | A fully qualified Java class name,or a type alias (see the table above for the list of built-in type aliases). MyBatis can usually figure out the type if you're mapping to a JavaBean. However,if you are mapping to a HashMap,then you should specify the javaType explicitly to ensure the desired behavIoUr. |
jdbcType | The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert,update or delete. This is a JDBC requirement,not a MyBatis one. So even if you were coding JDBC directly,you'd need to specify this type – but only for nullable values. |
typeHandler | We discussed default type handlers prevIoUsly in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation,or a type alias. |
Supported JDBC Types
For future reference,MyBatis supports the following JDBC Types via the included JdbcType enumeration.
BIT | FLOAT | CHAR | TIMESTAMP | OTHER | UNDEFINED |
TINYINT | REAL | VARCHAR | BINARY | BLOG | NVARCHAR |
SMALLINT | DOUBLE | LONGVARCHAR | VARBINARY | CLOB | NCHAR |
INTEGER | NUMERIC | DATE | LONGVARBINARY | BOOLEAN | NCLOB |
BIGINT | DECIMAL | TIME | NULL | CURSOR | ARRAY |
While properties will work for most Data Transfer Object (DTO) type classes,and likely most of your domain model,there may be some cases where you want to use immutable classes. Often tables that contain reference or lookup data that rarely or never changes is suited to immutable classes. Constructor injection allows you to set values on a class upon instantiation,without exposing public methods. MyBatis also supports private properties and private JavaBeans properties to achieve this,but some people prefer Constructor injection. Theconstructorelement enables this.
Consider the following constructor:
public class User { //... public User(int id,String username) { //... } //... }
In order to inject the results into the constructor,MyBatis needs to identify the constructor by the type of its parameters. Java has no way to introspect (or reflect) on parameter names. So when creating a constructor element,ensure that the arguments are in order,and that the data types are specified.
<constructor> <idArg column="id" javaType="int"/> <arg column="username" javaType="String"/> </constructor>
The rest of the attributes and rules are the same as for the regular id and result elements.
Attribute | Description |
---|---|
The column name from the database,204)">resultSet.getString(columnName). | |
The JDBC Type from the list of supported types that follows this table. The JDBC type is only required for nullable columns upon insert,not an MyBatis one. So even if you were coding JDBC directly,you'd need to specify this type – but only for nullable values. | |
We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of aTypeHandlerimplementation,or a type alias. | |
select | The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. See the Association element for more. |
resultMap | This is the ID of a ResultMap that can map the nested results of this argument into an appropriate object graph. This is an alternative to using a call to another select statement. It allows you to join multiple tables together into a singleResultSet. Such aResultSetwill contain duplicated,repeating groups of data that needs to be decomposed and mapped properly to a nested object graph. To facilitate this,MyBatis lets you "chain" result maps together,to deal with the nested results. See the Association element below for more. |
The association element deals with a "has-one" type relationship. For example,in our example,a Blog has one Author. An association mapping works mostly like any other result. You specify the target property,204)">javaTypeof the property (which MyBatis can figure out most of the time),the jdbcType if necessary and a typeHandler if you want to override the retrieval of the result values.
Where the association differs is that you need to tell MyBatis how to load the association. MyBatis can do so in two different ways:
- Nested Select: By executing another mapped SQL statement that returns the complex type desired.
- Nested Results: By using nested result mappings to deal with repeating subsets of joined results.
First,let's examine the properties of the element. As you'll see,it differs from a normal result mapping only by the select and resultMap attributes.
Attribute | Description |
---|---|
We discussed default type handlers previously in this documentation. Using this property you can override the default type handler on a mapping-by-mapping basis. The value is either a fully qualified class name of a TypeHandler implementation,or a type alias. |
Nested Select for Association
Attribute | Description |
---|---|
resultSet.getString(columnName). Note: To deal with composite keys,you can specify multiple column names to pass to the nested select statement by using the Syntaxcolumn="{prop1=col1,prop2=col2}". This will causeprop1andprop2to be set against the parameter object for the target nested select statement. | |
The ID of another mapped statement that will load the complex type required by this property mapping. The values retrieved from columns specified in the column attribute will be passed to the target select statement as parameters. A detailed example follows this table. Note: To deal with composite keys,204)">prop2to be set against the parameter object for the target nested select statement. | |
fetchType | Optional. Valid values arelazyandeager. If present,it supersedes the global configuration parameterlazyLoadingEnabledfor this mapping. |
For example:
<resultMap id="blogResult" type="Blog"> <association property="author" column="author_id" javaType="Author" select="selectAuthor"/> </resultMap> <select id="selectBlog" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectAuthor" resultType="Author"> SELECT * FROM AUTHOR WHERE ID = #{id} </select>
That's it. We have two select statements: one to load the Blog,the other to load the Author,and the Blog's resultMap describes that theselectAuthorstatement should be used to load its author property.
All other properties will be loaded automatically assuming their column and property names match.
While this approach is simple,it will not perform well for large data sets or lists. This problem is known as the "N+1 Selects Problem". In a nutshell,the N+1 selects problem is caused like this:
- You execute a single sql statement to retrieve a list of records (the "+1").
- For each record returned,you execute a select statement to load details for each (the "N").
This problem could result in hundreds or thousands of sql statements to be executed. This is not always desirable.
The upside is that MyBatis can lazy load such queries,thus you might be spared the cost of these statements all at once. However,if you load such a list and then immediately iterate through it to access the nested data,you will invoke all of the lazy loads,and thus performance could be very bad.
And so,there is another way.
Nested Select for Collection
First,let's look at using a nested select to load the Posts for the Blog.
<resultMap id="blogResult" type="Blog"> <collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/> </resultMap> <select id="selectBlog" resultMap="blogResult"> SELECT * FROM BLOG WHERE ID = #{id} </select> <select id="selectPostsForBlog" resultType="Post"> SELECT * FROM POST WHERE BLOG_ID = #{id} </select>
There are a number things you'll notice immediately,but for the most part it looks very similar to the association element we learned about above. First,you'll notice that we're using the collection element. Then you'll notice that there's a new "ofType" attribute. This attribute is necessary to distinguish between the JavaBean (or field) property type and the type that the collection contains. So you could read the following mapping like this:
<collection property="posts" javaType="ArrayList" column="id" ofType="Post" select="selectPostsForBlog"/>
Read as: "A collection of posts in an ArrayList of type Post."
ThejavaTypeattribute is really unnecessary,as MyBatis will figure this out for you in most cases. So you can often shorten this down to simply:
<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
Nested Results for Collection
By this point,you can probably guess how nested results for a collection will work,because it's exactly the same as an association,but with the same addition of theofTypeattribute applied.
First,let's look at the sql:
<select id="selectBlog" resultMap="blogResult"> select B.id as blog_id,from Blog B left outer join Post P on B.id = P.blog_id where B.id = #{id} </select>
Again,we've joined the Blog and Post tables,and have taken care to ensure quality result column labels for simple mapping. Now mapping a Blog with its collection of Post mappings is as simple as:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post"> <id property="id" column="post_id"/> <result property="subject" column="post_subject"/> <result property="body" column="post_body"/> </collection> </resultMap>
Again,remember the importance of the id elements here,or read the association section above if you haven't already.
Also,if you prefer the longer form that allows for more reusability of your result maps,you can use the following alternative mapping:
<resultMap id="blogResult" type="Blog"> <id property="id" column="blog_id" /> <result property="title" column="blog_title"/> <collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/> </resultMap> <resultMap id="blogPostResult" type="Post"> <id property="id" column="id"/> <result property="subject" column="subject"/> <result property="body" column="body"/> </resultMap>
Multiple ResultSets for Collection
As we did for the association,we can call an stored procedure that executes two queries and returns two result sets,one with Blogs and another with Posts:
SELECT * FROM BLOG WHERE ID = #{id} SELECT * FROM POST WHERE BLOG_ID = #{id}
A name must be given to each result set by adding a
We specify that the "posts" collection will be filled out of data contained in the result set named "posts":
<resultMap id="blogResult" type="Blog"> <id property="id" column="id" /> <result property="title" column="title"/> <collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id"> <id property="id" column="id"/> <result property="subject" column="subject"/> <result property="body" column="body"/> </collection> </resultMap>
NOTEThere's no limit to the depth,breadth or combinations of the associations and collections that you map. You should keep performance in mind when mapping them. Unit testing and performance testing of your application goes a long way toward discovering the best approach for your application. The nice thing is that MyBatis lets you change your mind later,with very little (if any) impact to your code.
Advanced association and collection mapping is a deep subject. Documentation can only get you so far. With a little practice,it will all become clear very quickly.