MyBatis輸入對映、輸出對映、動態SQL、關聯關係、Spring整合加強筆記
1. 計劃
1. 輸入對映
2. 輸出對映
3. 動態SQL
4. 關聯關係
5. Spring整合MyBatis
2. 輸入對映
2.1.1. 輸入對映-包裝pojo
我們假設建立一個身份證資訊實體類IdCard,IdCard裡包含一個屬性User引用,模擬這麼一個場景,加入我們知道一個Idcard,也知道IdCard的user.id屬性值,想傳入一個IdCard到後臺方法查詢出使用者詳細資訊。
2.1.1.1. 建立IdCard
public class IdCard { private String cartId private String city; private String province; private User user; //get..set… } |
2.1.1.2. 建立Mapper.xml對應的查詢方法
<!-- getByCardUser --> <select id="getByCardUser" parameterType="IdCard" resultType="User"> SELECT * FROM user WHERE id=#{user.id} </select> |
2.1.2. 輸入對映-Map
2.1.2.1. 建立測試方法
@Test public void getByMap(){ User user = new User(); user.setId("28"); Map<String, Object> dataMap = new HashMap<String,Object>(); dataMap.put("mp", user); User userinfo = sqlSession.getMapper(UserMapper.class).getByMap(dataMap); System.out.println(userinfo } |
2.1.2.2. 建立Mapper.xml方法
這裡和Pojo類似,都是key.property
<!-- getByMap --> <select id="getByMap" parameterType="map" resultType="User"> SELECT * FROM user WHERE id=#{mp.id} </select> |
3. 輸出對映
3.1. 簡單資料型別
3.1.1. 建立測試方法
建立一個方法,查詢總使用者記錄數
/*** * 輸出對映-簡單資料型別 */ @Test public void getCount(){ int count = sqlSession.getMapper(UserMapper.class).getAllCount(); System.out.println(count); } |
3.1.2. 建立Mapper.xml實現方法
<!-- getAllCount --> <select id="getAllCount" resultType="int"> SELECT COUNT(*) FROM user </select> |
3.1.3. 簡單資料類型別名參考表
參考類:org.apache.ibatis.type.TypeAliasRegistry
public TypeAliasRegistry() { this.registerAlias("string", String.class); this.registerAlias("byte", Byte.class); this.registerAlias("long", Long.class); this.registerAlias("short", Short.class); this.registerAlias("int", Integer.class); this.registerAlias("integer", Integer.class); this.registerAlias("double", Double.class); this.registerAlias("float", Float.class); this.registerAlias("boolean", Boolean.class); this.registerAlias("byte[]", Byte[].class); this.registerAlias("long[]", Long[].class); this.registerAlias("short[]", Short[].class); this.registerAlias("int[]", Integer[].class); this.registerAlias("integer[]", Integer[].class); this.registerAlias("double[]", Double[].class); this.registerAlias("float[]", Float[].class); this.registerAlias("boolean[]", Boolean[].class); this.registerAlias("_byte", Byte.TYPE); this.registerAlias("_long", Long.TYPE); this.registerAlias("_short", Short.TYPE); this.registerAlias("_int", Integer.TYPE); this.registerAlias("_integer", Integer.TYPE); this.registerAlias("_double", Double.TYPE); this.registerAlias("_float", Float.TYPE); this.registerAlias("_boolean", Boolean.TYPE); this.registerAlias("_byte[]", byte[].class); this.registerAlias("_long[]", long[].class); this.registerAlias("_short[]", short[].class); this.registerAlias("_int[]", int[].class); this.registerAlias("_integer[]", int[].class); this.registerAlias("_double[]", double[].class); this.registerAlias("_float[]", float[].class); this.registerAlias("_boolean[]", boolean[].class); this.registerAlias("date", Date.class); this.registerAlias("decimal", BigDecimal.class); this.registerAlias("bigdecimal", BigDecimal.class); this.registerAlias("biginteger", BigInteger.class); this.registerAlias("object", Object.class); this.registerAlias("date[]", Date[].class); this.registerAlias("decimal[]", BigDecimal[].class); this.registerAlias("bigdecimal[]", BigDecimal[].class); this.registerAlias("biginteger[]", BigInteger[].class); this.registerAlias("object[]", Object[].class); this.registerAlias("map", Map.class); this.registerAlias("hashmap", HashMap.class); this.registerAlias("list", List.class); this.registerAlias("arraylist", ArrayList.class); this.registerAlias("collection", Collection.class); this.registerAlias("iterator", Iterator.class); this.registerAlias("ResultSet", ResultSet.class); } |
3.2. ResultType
假如現在有這麼一個需求,資料庫name欄位變成了user_name,我們按照原來的查詢條件進行查詢
3.2.1. 測試類方法
/*** * 輸入Map */ @Test public void getByMap(){ User user = new User(); user.setId("28"); Map<String, Object> dataMap = new HashMap<String,Object>(); dataMap.put("mp", user); User userinfo = sqlSession.getMapper(UserMapper.class).getByMap(dataMap); System.out.println(userinfo); } |
結果
問題來了,我們採用ResultType,得到的資料部分屬效能進行轉換,但資料庫和實體bean之間屬性名不一樣的,無法進行轉換,如何解決?
這裡提供一種解決方案:可以在查詢的時候取別名
例如 select id,user_name name,agefrom user where id=28
3.2.2. 修改Mapper.xml查詢方法
<!-- getByMap --> <select id="getByMap" parameterType="map" resultType="User"> SELECT id,user_name name,age FROM user WHERE id=#{mp.id} </select> |
測試結果:
3.3. ResultMap
這裡還提供另外一種解決方案,ResultMap,ResultMap更想Hibernate裡面的hbm.xml檔案的對映功能,提供了資料庫表字段和實體Bean之間屬性的一種對映關係,然後根據對映關係進行轉換。
3.3.1. 修改Mapper.xml
3.3.1.1. 增加<resultMap>節點
這裡的id節點是個特殊節點,指和資料庫之間主鍵對映的屬性,這裡它類似Set集合,不允許id節點在對應的集合裡出現重複。
result則是普通對映關係。
<resultMap type="User" id="baseResultMap"> <id column="id" property="id" javaType="java.lang.String"/> <result column="user_name" property="name" javaType="java.lang.String"/> <result column="age" property="age" javaType="int" /> </resultMap> |
3.3.1.2. 執行測試
問題來了,大家思考一下那麼ResultMap是什麼原理呢?
4. 動態SQL
4.1. If
在動態 SQL 中所做的最通用的事情是包含部分 where 字句的條件。比如:
<!-- getByMap --> <select id="getByMap" parameterType="map" resultMap="baseResultMap"> SELECT id,user_name,age FROM user WHERE 1=1 <if test="mp.id!=null"> AND id=#{mp.id} </if> </select> |
這條語句會提供一個可選的文字查詢功能。如果你沒有傳遞 id,那麼所有資料將會被查出。
假若我們想可選搜尋id 和 name 呢?首先,要改變語句的名稱讓它有意義。然後 簡單加入另外的一個條件。
<!-- getByMap --> <select id="getByMap" parameterType="map" resultMap="baseResultMap"> SELECT id,user_name,age FROM user WHERE 1=1 <if test="mp.id!=null"> AND id=#{mp.id} </if> <if test="mp.name!=null"> AND user_name=#{mp.name} </if> </select> |
4.2. choose, when, otherwise
有時我們不想應用所有的條件, 相反我們想選擇很多情況下的一種。 Java 中的 switch 和 語句相似,MyBatis 提供 choose 元素。
@Test public void getByDySQL(){ User user = new User("%小紅%",15); List<User> users = sqlSession.getMapper(UserMapper.class).getByDySQL(user); for (User u : users) { System.out.println(u); } } |
<!-- getByDySQL --> <select id="getByDySQL" parameterType="User" resultType="User"> SELECT * FROM user WHERE 1=1 <choose> <when test="age>15"> <![CDATA[ AND age>#{age} ]]> </when> <when test="name!=null"> <![CDATA[ AND age<=#{age} AND user_name like #{name} ]]> </when> <otherwise> AND age=#{age} </otherwise> </choose> </select> |
4.3. trim, where, set
前面的例子已經方便地處理了一個臭名昭著的動態 SQL 問題。要考慮我們回到“if”示 例後會發生什麼,如果用if,一個條件都不成立這時候就會有問題。如:id=null,name=null的情況下,SQL語句就是個病句。
<!-- getByMap --> <select id="getByMap" parameterType="map" resultMap="baseResultMap"> SELECT id,user_name,age FROM user WHERE <if test="mp.id!=null"> AND id=#{mp.id} </if> <if test="mp.name!=null"> AND user_name=#{mp.name} </if> </select> |
where 元素知道如果由被包含的標記返回任意內容,就僅僅插入“WHERE” 。而且,如 果以“AND”或“OR”開頭的內容,那麼就會跳過 WHERE 不插入。
<!-- getByDySQL --> <select id="getByDySQL" parameterType="User" resultType="User"> SELECT * FROM user <where> <if test="age>15"> age>#{age} </if> <if test="name!=null"> user_name like #{name} </if> </where> </select> |
如果 where 元素沒有做出你想要的,你可以使用 trim 元素來自定義。比如,和 where 元素相等的 trim 元素是:
<!-- getByDySQL --> <select id="getByDySQL" parameterType="User" resultType="User"> SELECT * FROM user <trim prefix="WHERE" prefixOverrides="AND|OR"> <if test="age>15"> OR age>#{age} </if> <if test="name!=null"> AND user_name like #{name} </if> </trim> </select> |
結果
針對修改而言,我們還有另一種操作方式,set標籤
<!-- update --> <update id="update" parameterType="User"> UPDATE user <set> <if test="age>0"> age=#{age}, </if> <if test="name!=null"> user_name=#{name}, </if> </set> WHERE id=#{id} </update> |
4.4. foreach*
另外一個動態 SQL 通用的必要操作是迭代一個集合, 通常是構建在 IN 條件中的。 比如:
@Test public void testForeachList(){ List<User> users = new ArrayList<User>(); users.add(new User("18")); users.add(new User("21")); users.add(new User("25")); List<User> alluser = sqlSession.getMapper(UserMapper.class).getByIdList(users); for (User user : alluser) { System.out.println(user); } } |
<!-- getByIdList --> <select id="getByIdList" parameterType="User" resultMap="baseResultMap"> SELECT * FROM user WHERE id IN <foreach collection="list" item="item" open="(" close=")" separator=","> #{item.id} </foreach> </select> |
foreach 元素是非常強大的,它允許你指定一個集合,宣告集合項和索引變數,它們可 以用在元素體內。它也允許你指定開放和關閉的字串,在迭代之間放置分隔符。這個元素 是很智慧的,它不會偶然地附加多餘的分隔符。
注意 你可以傳遞一個 List 例項或者陣列作為引數物件傳給 MyBatis。當你這麼做的時 候,MyBatis 會自動將它包裝在一個 Map 中,用名稱在作為鍵。List 例項將會以“list” 作為鍵,而陣列例項將會以“array”作為鍵。
這個部分是對關於 XML 配置檔案和 XML 對映檔案的而討論的。下一部分將詳細討論 Java API,所以你可以得到你已經建立的最有效的對映。
4.5. Sql片段
比如一張表字段比較多,但又不是所有的都常用,可以寫一個sql節點,裡面寫一些常常要用到的sql語句,提供在其他地方呼叫,如下:
<!-- SQL片段 --> <sql id="Base_Cloumn_List"> id,user_name,age </sql> |
通過<include refid=”x”/>來引用
<!-- getByIdList --> <select id="getByIdList" parameterType="User" resultMap="baseResultMap"> SELECT <include refid="Base_Cloumn_List" /> FROM user WHERE id IN <foreach collection="list" item="item" open="(" close=")" separator=","> #{item.id} </foreach> </select> |
5. 關聯關係
5.1. 一對一關聯
5.1.1. 分析
一對一關聯關係,我們舉個例子,比如有2張表,一張User表,一張IdCard表,一張身份證資訊對應一個人,或者叫一個人只有一張身份證資訊都行。那程式中如何去實現一對一關聯關係?
我們分析大概有2種,一種是一條SQL通過關聯關係全部查出來,另一種是先查詢出一條來,再根據這一條查詢關聯物件。
1、 select u.*,card.* from user u inner join IdCard card on u.id=card.userid 2、 select * from user where id=12 select * from IdCard where userid=12 |
5.1.2. association關聯查詢
association一個複雜的型別關聯;許多結果將包成這種型別,嵌入結果對映 – 結果對映自身的關聯,或者參考一個
<resultMap type="IdCard" id="IdCardResultMap"> <id column="cartId" property="cartId"/> <result column="province" property="province" /> <result column="city" property="city" /> <!-- 一對一對映關係 --> <association property="user" javaType="User"> <id column="id" property="id"/> <result column="name" property="name" /> <result column="age" property="age" /> </association> </resultMap> |
5.1.2.1. 多次查詢實現一對一
Mapper.xml程式碼實現
這裡程式碼可以理解成< association select=”getUserById”>當前節點的資料通過呼叫getUserById的節點得到,和我們前面所想的先查詢出身份證資訊,再查出使用者資訊分兩個步驟。
<resultMap type="IdCard" id="IdCardResultMap"> <id column="cartId" property="cartId"/> <result column="province" property="province" /> <result column="city" property="city" /> <!-- 一對一對映關係 --> <association property="user" javaType="User" select="getUserById" column="userid"> <id column="id" property="id"/> <result column="user_name" property="name" /> <result column="age" property="age" /> </association> </resultMap> <!-- getCard --> <select id="getCard" parameterType="java.lang.String" resultMap="IdCardResultMap"> SELECT * FROM IdCard WHERE cartId=#{cartId} </select> <!-- getUserById --> <select id="getUserById" parameterType="java.lang.String" resultType="User"> SELECT id,user_name name,age FROM user WHERE id=#{id} </select> |
輸出結果有2條SQL語句,說明做了2次查詢。
5.1.2.2. 一對一一次關聯查詢
上面的步驟分為了2次實現了一對一查詢,那紅做法和我們通常寫java程式碼實現思路一致,但有沒有什麼方法能夠實現一次查詢就能得到結果呢?也可以,例如寫個關聯查詢,再封裝一個和關聯查詢對應的結果集的實體Bean。當然這種做法不科學,可以利用MyBatis提供的association節點來完成。
步驟如下:
先寫一個關聯查詢SQL
建立實體Bean之間的關聯關係
建立resultMap之間的對映關係,通過association來建立對映
SQL關聯查詢
SELECT card.*, u.* FROM idcard card INNER JOIN user u ON card.userid=u.id WHERE Card.cartId=#{cartId} |
ResultMap對映
這裡只需要指定對應的屬性property和當前association需要轉換的java型別即可。
<resultMap type="IdCard" id="IdCardResultMap"> <id column="cartId" property="cartId"/> <result column="province" property="province" /> <result column="city" property="city" /> <!-- 一對一對映關係 --> <association property="user" javaType="User"> <id column="id" property="id"/> <result column="user_name" property="name" /> <result column="age" property="age" /> </association> </resultMap> |
將對應的SQL語句寫到對應的節點裡
<!-- getCard --> <select id="getCard" parameterType="java.lang.String" resultMap="IdCardResultMap"> SELECT card.*, u.* FROM idcard card INNER JOIN user u ON card.userid=u.id WHERE cartId=#{id} </select> |
5.2. 一對多關聯
5.2.1. 分析
剛才我們完成了一對一的關聯關係管理,那麼如果有一對多如何管理呢?MyBatis提供了ResultMap的子節點collection來管理一對多對映。它的流程和步驟與一對一關聯對映處理一樣,也有2種情況,分別為多次查詢匹配結果和單次查詢從結果集物件裡組合一對多資訊
假設有這麼一種場景,在上述情況下,增加一個Mobile物件,一個人有多部手機,如何實現?
5.2.2. 建立Mobile表和實體Bean
CREATE TABLE `mobile` ( `mid` varchar(50) NOT NULL, `mname` varchar(50) NOT NULL, `userid` varchar(50) NOT NULL, PRIMARY KEY (`mid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; |
public class Mobile { private String mid; private String mname; //get set… } |
5.2.3. Collection一對多關聯查詢
Mapper.xml對映配置資訊
在剛才的基礎上加入collection節點,如下,呼叫getByUserId來查詢手機資訊
<resultMap type="IdCard" id="IdCardResultMap"> <id column="cartId" property="cartId"/> & |