MyBatis中在查詢進行select映射的時候,返回類型可以用resultType,也可以用resultMap,resultType是直接
表示返回類型的,而resultMap則是對外部ResultMap的引用,但是resultType跟resultMap不能同時存在。
1.resultType
在MyBatis進行查詢映射的時候,其實查詢出來的每一個屬性都是放在一個對應的Map裏面的,其中鍵是屬性名,
值則是其對應的值。當提供的返回類型屬性是resultType的時候,MyBatis會將Map裏面的鍵值對取出賦給
resultType所指定的對象對應的屬性。所以其實MyBatis的每一個查詢映射的返回類型都是ResultMap,
只是當我們提供的返回類型屬性是resultType的時候,MyBatis對自動的給我們把對應的值賦給resultType
所指定對象的屬性,而當我們提供的返回類型是resultMap的時候,因爲Map不能很好表示領域模型,
我們就需要自己再進一步的把它轉化爲對應的對象,這常常在複雜查詢中很有作用。
這裏要強調的是,Mybatis是對返回的結果的每一行做映射的。所以,下面的語句返回的是Integer,而不是List
- <select id="count" parameterType="AreaDto" resultType="java.lang.Integer">
- SELECT id FROM USER
- </select>
返回一個int
- <select id="count" parameterType="AreaDto" resultType="java.lang.Integer">
- SELECT count(*) FROM USER
- </select>
返回map
- <select id=”selectUsers” parameterType=”int” resultType=”hashmap”>
- select id, username, hashedPassword
- from some_table
- where id = #{id}
- </select>
這樣一個語句簡單作用於所有列被自動映射到HashMap的鍵上,這由resultType屬性指定。這在很多情況下是有用的,但是HashMap不能很好描述一個領域模型。那樣你的應用程序將會使用JavaBeans或POJOs(Plain Old Java Objects,普通Java對象)來作爲領域模型
返回javaBEAN 對象
- <select id="count" parameterType="AreaDto" resultType="User">
- SELECT * FROM USER
- </select>
要記住類型別名是你的夥伴。使用它們你可以不用輸入類的全路徑。
- <typeAlias type=”com.someapp.model.User” alias=”User”/>
這些情況下,MyBatis會在幕後自動創建一個ResultMap,基於屬性名來映射列到JavaBean的屬性上
2.resultMap
MyBatis會自動創建一個ResultMap對象,然後基於查找出來的屬性名進行鍵值對封裝,然後再看到返回類型是Blog對象,再從ResultMap中取出與Blog對象對應的鍵值對進行賦值。
當返回類型直接是一個ResultMap的時候也是非常有用的,這主要用在進行復雜聯合查詢上,因爲進行簡單查詢是沒有什麼必要的。
簡單resultMap配置
- <resultMap type="com.liulanghan.Blog" id="BlogResult">
- <id column="id" property="id"/>
- <result column="title" property="title"/>
- <result column="content" property="content"/>
- <result column="owner" property="owner"/>
- </resultMap>
- <select id="selectBlog" parameterType="int" resultMap="BlogResult">
- select * from t_blog where id = #{id}
- </select>
結果集的列比resultMap多會報錯麼?
不會,只映射resultMap中有的列。
結果集的列比resultMap少會報錯麼?
不會,只映射結果集中有的列。
高級結果映射
- <resultMap id="detailedBlogResultMap" type="Blog">
- <constructor>
- <idArg column="blog_id" javaType="int"/>
- </constructor>
- <result property="title" column="blog_title"/>
- <association property="author" column="blog_author_id" 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" column="post_author_id" javaType="Author"/>
- <collection property="comments" column="post_id" ofType=" Comment">
- <id property="id" column="comment_id"/>
- </collection>
- <collection property="tags" column="post_id" ofType=" Tag" >
- <id property="id" column="tag_id"/>
- </collection>
- <discriminator javaType="int" column="draft">
- <case value="1" resultType="DraftPost"/>
- </discriminator>
- </collection>
- </resultMap>
resultMap
constructor – 類在實例化時,用來注入結果到構造方法中
idArg – ID參數;標記結果作爲ID可以幫助提高整體效能
arg – 注入到構造方法的一個普通結果
id – 一個ID結果;標記結果作爲ID可以幫助提高整體效能
result – 注入到字段或JavaBean屬性的普通結果
association – 一個複雜的類型關聯;許多結果將包成這種類型
嵌入結果映射 – 結果映射自身的關聯,或者參考一個
collection – 複雜類型的集
嵌入結果映射 – 結果映射自身的集,或者參考一個
discriminator – 使用結果值來決定使用哪個結果映射
case – 基於某些值的結果映射
嵌入結果映射 – 這種情形結果也映射它本身,因此可以包含很多相同的元素,或者它可以參照一個外部的結果映射。
id 和result
id和result都映射一個單獨列的值到簡單數據類型
這兩者之間的唯一不同是id表示的結果將是當比較對象實例時用到的標識屬性。這幫助來改進整體表現,特別是緩存和嵌入結果映射(也就是聯合映射)。
它們共有的屬性如下:
property
映射到列結果的字段或屬性。如果匹配的是存在的,和給定名稱相同的JavaBeans的屬性,那麼就會使用。否則MyBatis將會尋找給定名稱的字段。這兩種情形你可以使用通常點式的複雜屬性導航。比如,你可以這樣映射一些東西:“username”,或者映射到一些複雜的東西:“address.street.number”。
column
從數據庫中得到的列名,或者是列名的重命名標籤。這也是通常和會傳遞給resultSet.getString(columnName)方法參數中相同的字符串。
javaType
一個Java類的完全限定名,或一個類型別名(參加上面內建類型別名的列表)。如果你映射到一個JavaBean,MyBatis通常可以斷定類型。然而,如果你映射到的是HashMap,那麼你應該明確地指定javaType來保證所需的行爲。
jdbcType
在這個表格之後的所支持的JDBC類型列表中的類型。JDBC類型是僅僅需要對插入,更新和刪除操作可能爲空的列進行處理。這是JDBC的需要,而不是MyBatis的。如果你直接使用JDBC編程,你需要指定這個類型-但僅僅對可能爲空的值。
typeHandler
我們在前面討論過默認的類型處理器。使用這個屬性,你可以覆蓋默認的類型處理器。這個屬性值是類的完全限定名或者是一個類型處理器的實現,或者是類型別名。
constructor
。構造方法注入允許你在初始化時爲類設置屬性的值,而不用暴露出公有方法。MyBatis也支持私有屬性和私有JavaBeans屬性來達到這個目的,但是一些人更青睞構造方法注入。
爲了向這個構造方法中注入結果,MyBatis需要通過它的參數的類型來標識構造方法。Java沒有自查(反射)參數名的方法。所以當創建一個構造方法元素時,保證參數是按順序排列的,而且數據類型也是確定的。
association
association關聯元素處理“有一個”類型的關係,即一對一關聯。它有兩種關聯方式
嵌套查詢:通過執行另外一個SQL映射語句來返回預期的複雜類型。
嵌套結果:使用嵌套結果映射來處理重複的聯合結果的子集。
嵌套查詢
- <resultMap id="userResultMap" type="User">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="loginName" column="LOGIN_NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="password" column="password" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="roleId" column="role_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <association property="role" column="role_id" javaType="Role" select="selectRole"/>
- </resultMap>
- <select id="selectUser" parameterType="java.lang.Long" resultMap="userResultMap" >
- select * from User where id =#{id}
- </select>
- <select id="selectRole" parameterType="java.lang.Long" resultType="Role" >
- select * from Role where id =#{id}
- </select>
這裏有兩個查詢,一個查詢加載User,一個查詢加載Role.
這裏select爲另外一個映射語句的ID,可以加載這個屬性映射需要的複雜類型。獲取的在列屬性中指定的列的值將被傳遞給目標select語句作爲參數。
注意:
而select 爲selectRole的SQL輸入參數可以隨便給名稱,只要是輸入參數與壓入進去的值類型相同就行了,可以寫成:
- select * from Role where id = #{sfffs}
不管輸入參數名稱是什麼,mybatis最終會執行:
效果爲:
- select * from role where id =resultSet.getLong("Role_id");
注意:要保證第二個查詢查出來的結果只有一條記錄。
要處理複合主鍵,你可以指定多個列名通過column="{prop1=col1,prop2=col2}"這種語法來傳遞給嵌套查詢語句。這會引起prop1和prop2以參數對象形式來設置給目標嵌套查詢語句。
- <resultMap id="userResultMap" type="User">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="loginName" column="LOGIN_NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="password" column="password" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="roleId" column="role_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <association property="role" column="{id=role_id,name=role_name}" javaType="Role" select="selectRole"/>
- </resultMap>
- <select id="selectRole" parameterType="HashMap" resultType="Role" >
- select * from Role where id =#{id} and name= #{name}
- </select>
這種方式很簡單,但是對於大型數據集合和列表將不會表現很好。問題就是我們熟知的“N+1查詢問題”。概括地講,N+1查詢問題可以是這樣引起的:
你執行了一個單獨的SQL語句來獲取結果列表(就是“+1”)。
對返回的每條記錄,你執行了一個查詢語句來爲每個加載細節(就是“N”)。
這個問題會導致成百上千的SQL語句被執行。這通常不是期望的。
比如一個查詢用戶列表的SQL,假如有2000個用戶,那麼就是一個查詢用戶的SQL和2000個查詢角色的SQL,一共有2001個SQL被運行。
MyBatis能延遲加載這樣的查詢就是一個好處,因此你可以分散這些語句同時運行的消耗。然而,如果你加載一個列表,之後迅速迭代來訪問嵌套的數據,你會調用所有的延遲加載,這樣的行爲可能是很糟糕的。
所以還有另外一種方法。
關聯的嵌套結果
嵌套結果
- <resultMap id="userResultMap" type="User">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="loginName" column="LOGIN_NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="password" column="password" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="roleId" column="role_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <association property="role" column="role_id" javaType="Role" resultMap="roleResultMap"/>
- <id property="id" column="role_id"/>
- <result property="name" column="role_name"/>
- </association>
- </resultMap>
- <resultMap id="roleResultMap" type="Role">
- <id property="id" column="role_id"/>
- <result property="name" column="role_name"/>
- </resultMap>
也可以這樣配置
- <resultMap id="userResultMap" type="User">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="loginName" column="LOGIN_NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="password" column="password" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="roleId" column="role_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <association property="role" column="role_id" javaType="Role" resultMap="roleResultMap"/>
- </resultMap>
- <resultMap id="roleResultMap" type="Role">
- <id property="id" column="role_id"/>
- <result property="name" column="role_name"/>
- </resultMap>
resultMap這是結果映射的ID,可以映射關聯的嵌套結果到一個合適的對象圖中。這是一種替代方法來調用另外一個查詢語句。這允許你聯合多個表來合成到一個單獨的結果集。這樣的結果集可能包含重複,數據的重複組需要被分解,合理映射到一個嵌套的對象圖。爲了使它變得容易,MyBatis讓你“鏈接”結果映射,來處理嵌套結果。一個例子會很容易來仿照,這個表格後面也有一個示例。
注意這個聯合查詢,以及採取保護來確保所有結果被唯一而且清晰的名字來重命名。
columnPrefix 屬性
- <association property="role" column="role_id" javaType="Role" resultMap="roleResultMap" columnPrefix="role_"/>
- <id property="id" column="id"/>
- <result property="name" column="name"/>
- </association>
非常重要:在嵌套據誒過映射中id元素扮演了非常重要的角色。應應該通常指定一個或多個屬性,它們可以用來唯一標識結果。實際上就是如果你離開她了,但是有一個嚴重的性能問題時MyBatis仍然可以工作。選擇的屬性越少越好,它們可以唯一地標識結果。主鍵就是一個顯而易見的選擇(儘管是聯合主鍵)。
上面你已經看到了如何處理“有一個”類型關聯。但是“有很多個”是怎樣的?下面這個部分就是來討論這個主題的。
collection
collection關聯元素處理一對多關聯。
- <resultMap id="roleResultMap" type="Role">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="name" column="NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="userId" column="user_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <collection property="user" column="user_id" javaType="ArrayList" ofType="Post" select="selectUser"/>
- </resultMap>
- <select id="selectUser" parameterType="java.lang.Long" resultType="User" >
- select * from uer where id =#{id}
- </select>
同樣,可以這樣配置
- <resultMap id="roleResultMap" type="Role">
- <id property="id" column="ID" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <result property="name" column="NAME" jdbcType="VARCHAR" javaType="java.lang.String"/>
- <result property="userId" column="user_id" jdbcType="NUMERIC" javaType="java.lang.Long"/>
- <collection property="user" column="user_id" javaType="ArrayList" ofType="Post">
- <id property="id" column="user_id"/>
- <result property="name" column="user_name"/>
- </collection>
- </resultMap>
- <resultMap id="userResultMap" type="User">
- <id property="id" column="user_id"/>
- <result property="name" column="user_name"/>
- </resultMap>
- 另外一處的例子:
-
基礎部分可以查看我的另一篇博客http://haohaoxuexi.iteye.com/blog/1333271
MyBatis中在查詢進行select映射的時候,返回類型可以用resultType,也可以用resultMap,resultType是直接表示返回類型的,而resultMap則是對外部ResultMap的引用,但是resultType跟resultMap不能同時存在。在MyBatis進行查詢映射的時候,其實查詢出來的每一個屬性都是放在一個對應的Map裏面的,其中鍵是屬性名,值則是其對應的值。當提供的返回類型屬性是resultType的時候,MyBatis會將Map裏面的鍵值對取出賦給resultType所指定的對象對應的屬性。所以其實MyBatis的每一個查詢映射的返回類型都是ResultMap,只是當我們提供的返回類型屬性是resultType的時候,MyBatis對自動的給我們把對應的值賦給resultType所指定對象的屬性,而當我們提供的返回類型是resultMap的時候,因爲Map不能很好表示領域模型,我們就需要自己再進一步的把它轉化爲對應的對象,這常常在複雜查詢中很有作用。
有這樣一個Blog.java文件
- import java.util.List;
- public class Blog {
- private int id;
- private String title;
- private String content;
- private String owner;
- private List<Comment> comments;
- // getter setter ...
-
- }
其所對應的數據庫表中存儲有id,title,Content,Owner屬性,那麼當我們進行下面這樣一個查詢映射的時候
- <typeAlias alias="Blog" type="com.tiantian.mybatis.model.Blog"/><!--來自MyBatis的配置文件mybatis_config.xml-->
- <select id="selectBlog" parameterType="int" resultType="Blog">
- select * from t_blog where id = #{id}
- </select><!--來自SQL映射文件BlogMapper.xml-->
MyBatis會自動創建一個ResultMap對象,然後基於查找出來的屬性名進行鍵值對封裝,然後再看到返回類型是Blog對象,再從ResultMap中取出與Blog對象對應的鍵值對進行賦值。
當返回類型直接是一個ResultMap的時候也是非常有用的,這主要用在進行復雜聯合查詢上,因爲進行簡單查詢是沒有什麼必要的。我們先看看一個返回類型爲ResultMap的簡單查詢,再看看複雜查詢的用法。
簡單查詢的寫法
- <resultMap type="Blog" id="BlogResult">
- <id column="id" property="id"/>
- <result column="title" property="title"/>
- <result column="content" property="content"/>
- <result column="owner" property="owner"/>
- </resultMap>
- <select id="selectBlog" parameterType="int" resultMap="BlogResult">
- select * from t_blog where id = #{id}
- </select>
select映射中resultMap的值是一個外部resultMap的id,表示返回結果映射到哪一個resultMap上,外部resultMap的type屬性表示該resultMap的結果是一個什麼樣的類型,這裏是Blog類型,那麼MyBatis就會把它當作一個Blog對象取出。resultMap節點的子節點id是用於標識該對象的id的,而result子節點則是用於標識一些簡單屬性的,其中的Column屬性表示從數據庫中查詢的屬性,Property則表示查詢出來的屬性對應的值賦給實體對象的哪個屬性。簡單查詢的resultMap的寫法就是這樣的。接下來看一個複雜一點的查詢。
有一個Comment類,其中有一個Blog的引用,表示是對哪個Blog的Comment,那麼我們在查詢Comment的時候把其對應的Blog也要查出來賦給其blog屬性。
- import java.util.Date;
- public class Comment {
- private int id;
- private String content;
- private Date commentDate = new Date();
- private Blog blog;
- // getter setter ...
- }
其寫法是這樣的
- <!--來自CommentMapper.xml文件 -->
- <resultMap type="Comment" id="CommentResult">
- <association property="blog" select="selectBlog" column="blog" javaType="Blog"/>
- </resultMap>
- <select id="selectComment" parameterType="int" resultMap="CommentResult">
- select * from t_Comment where id = #{id}
- </select>
- <select id="selectBlog" parameterType="int" resultType="Blog">
- select * from t_Blog where id = #{id}
- </select>
其訪問情況是這樣的,先是請求id爲selectComment的select映射,然後得到一個id爲CommentResult的ResultMap對象,我們可以看到在對應的resultMap的返回類型是一個Comment對象,其中只有一個association節點,而沒有像前面說的簡單查詢所對應的id,result子節點,但是其仍會把對應的id等屬性賦給Comment對象,這就是前面所說的MyBatis擁有自動封裝功能,只要你提供了返回類型,MyBatis會根據自己的判斷來利用查詢結果封裝對應的對象,所以前面的簡單查詢中,如果你不在resultMap中明確的指出id對應哪個字段,title對應哪個字段,MyBatis也會根據自身的判斷來幫你封裝,MyBatis的自身判斷是把查詢的field或其對應的別名與返回對象的屬性進行比較,如果相匹配且類型也相匹配,MyBatis則會對其進行賦值。在上面對應的resultMap中關聯了一個blog屬性,其對應的JAVA類型爲Blog,在上述的寫法中,關聯對象是通過子查詢來進行關聯的,當然也可以直接通過關聯查詢來進行關聯。上面的association子節點中,Property屬性表示是resultMap返回類型的哪個關聯屬性,對於上面的例子就是Comment管理的blog屬性;select表示進行哪個select映射來映射對應的關聯屬性,即會去請求id爲select所對應的值的select映射 來查詢出其所關聯的屬性對象;Column表示當前關聯對象在id爲CommentResult的resultMap中所對應的鍵值對,該鍵值對將作爲對關聯對象子查詢的參數,即將把在selectComment中查詢出來的blog屬性的值作爲參數傳給進行關聯對象blog的子查詢selectBlog的參數;javaType表示當前關聯對象在JAVA中是什麼類型。
上述介紹的是一對一或一對多的情況下,對一的一方的關聯的查詢。在實際應用中還有一個用的比較多的應用是通過一的一方查出對應的多的一方,在拿出多的一方的時候也同樣要把一的一方關聯上,即在上述例子中,在拿出Blog對象的時候,就把其對應的Comment全部拿出來,在拿出Comment的時候也還是需要把其對應的Blog拿出來,這是在JAVA中通過一次請求就拿出來的。寫法如下:
- <!-- 來自BlogMapper.xml文件 -->
- <resultMap type="Blog" id="BlogResult">
- <id column="id" property="id"/>
- <collection property="comments" select="selectCommentsByBlog" column="id" ofType="Comment"></collection>
- </resultMap>
- <resultMap type="Comment" id="CommentResult">
- <association property="blog" javaType="Blog" column="blog" select="selectBlog"/>
- </resultMap>
- <select id="selectBlog" parameterType="int" resultMap="BlogResult">
- select * from t_blog where id = #{id}
- </select>
- <!-- 通過Blog來查找Comment -->
- <select id="selectCommentsByBlog" parameterType="int" resultMap="CommentResult">
- select * from t_Comment where blog = #{blogId}
- </select>
上述請求的入口是id爲selectBlog的select映射,返回結果爲id爲BlogResult的resultMap,id爲BlogResult的類型爲Blog,其中指定了id的屬性和字段,指定id將對MyBatis內部的構造作用非常大。其中關聯了一個comments對象,因爲一個Blog可以有很多Comment,該comments爲一個集合,所以用集合collection進行映射,其中的select還是表示進行哪個子查詢來查詢對應的comments,column表示把上述查出來的哪個字段值當作參數傳給子查詢,ofType也是表示返回類型,這裏的返回類型是集合內部的類型,之所以用ofType而不是用type是MyBatis內部爲了和關聯association進行區別。
測試代碼:
- @Test
- public void selectCommentsByBlogTest() {
- SqlSession session = Util.getSqlSessionFactory().openSession();
- CommentMapper commentMapper = session.getMapper(CommentMapper.class);
- List<Comment> comments = commentMapper.selectCommentsByBlog(6);
- for (Comment comment : comments)
- System.out.println(comment);
- session.close();
- }
- /**
- * 查詢單條記錄
- */
- @Test
- public void testSelectOne() {
- SqlSession session = Util.getSqlSessionFactory().openSession();
- BlogMapper blogMapper = session.getMapper(BlogMapper.class);
- Blog blog = blogMapper.selectBlog(6);
- List<Comment> comments = blog.getComments();
- if (comments != ) {
- System.out.println("--------------Comments Size------------" + comments.size());
- for (Comment comment : comments)
- System.out.println(comment);
- }
- session.close();
- }