標籤:style java color 使用 資料 os
上一章我們講到,如果說hibernate是物件導向為主,關係為輔,那麼在mybatis中則是著重考慮的是關聯式模式,換句話說,如果物件模型設計的不好,就會很容易的感覺到實現的難度。
首先來看看最簡單的單向many2one:
建立對象:
public class Customer {
private Long id;
private String name;
}
public class Orders {
private Long id;
private String sn;
private Double price;
private Customer customer;
}
很簡單,Orders和Customer是一個單向的many2one的關係。和hibernate一樣,我們先來配置簡單的customer:
建立CustomerMapper.xml:
<mapper namespace="cd.itcast.mybatis.customer">
<insert id="save" keyProperty="id" parameterType="Customer"
useGeneratedKeys="true">
INSERT INTO customer(name) values (#{name})
</insert>
<select id="get" resultType="Customer" parameterType="int">
SELECT * FROM customer WHERE id = #{id}
</select>
</mapper>
只是一個很簡單的單對象映射操作。
在hibernate的many2one中我們知道,在many方的外部索引鍵關聯到one方的主鍵。在many方儲存的時候,應該在對應外鍵儲存關聯one方的id。所以,many方的儲存insert應該是這樣:
OrdersMapper.xml:
<insert id="save" keyProperty="id" parameterType="Orders"
useGeneratedKeys="true">
INSERT INTO orders(sn,price,customer_id) values (#{sn},#{price},#{customer.id})
</insert>
在這裡看到,我們在對應外鍵(customer_id)的地方,使用#{customer.id}來代表orders的customer屬性的id屬性,mybatis會像EL那樣自動幫我們得到值。所以,在這裡一定要注意儲存對象的順序,必須是先儲存one再儲存many方。在hibernate中,如果儲存順序有誤,hibernate會在提交事務的時候同步髒資料,幫我們補一條update語句來保證對象的關係正確,但是在mybaits中,所有的sql都由我們來完成,所以沒人會幫我們來完成update。所以,對應的儲存測試應該是:
@Test
public void testSave(){
SqlSession session=MyBatisUtil.openSession();
Customer c=new Customer();
c.setName("itcasT");
Orders o=new Orders();
o.setPrice(800d);
o.setSn("001");
o.setCustomer(c);
session.insert("cd.itcast.mybatis.customer.save",c);
session.insert("cd.itcast.mybatis.orders.save",o);
session.commit();
session.close();
}
下面就是many方的擷取了。如果只是簡單的把orders配置成一個單對象,即使用resultType來映射:
<select id="get" parameterType="long" resultType="Orders">
SELECT * FROM orders WHERE o.id = #{id}
</select>
如果是這樣,在測試的時候:
@Test
public void testGet(){
SqlSession session=MyBatisUtil.openSession();
Orders o=session.selectOne("cd.itcast.mybatis.orders.get", 1l);
System.out.println(o);
Customer c=o.getCustomer();
System.out.println(c);
session.close();
}
會看到,orders對應的customer是null,只能說明mybatis不能幫我們自動完成相關的關係映射。下面就來看下在mybatis中的關係映射:
第一種方式:使用額外的sql:
<select id="get" parameterType="int" resultMap="ordersmap">
SELECT * FROM orders WHERE o.id = #{id}
</select>
在這裡,添加resultMap來完成相關的映射:
<resultMap type="Orders" id="ordersmap">
<id property="id" column="id"/>
<result property="sn" column="sn"/>
<result property="price" column="price"/>
<association property="customer" column="customer_id" javaType="Customer"
select="cd.itcast.mybatis.customer.get">
</resultMap>
可能第一次看這個配置會覺得很奇怪,其實很好理解:
1,association:在mybatis中不像hibernate那樣,把關係分的很細緻,比如one-to-one,many-to-one,而只是把關係分成了兩種:第一種是對單對象的關係,就用association,一種是集合的關係,這個待會再看。因為這裡orders只是把customer對象作為自己的一個屬性,所以使用association來表示這個關係;
2,property:和hibernate一樣,定義這個關係在orders對象上面的對應屬性。在完成關聯對象的映射後,會使用這個屬性把對象設定到orders對象上;
3,column:代表從查詢結果集(select * from orders where id = ?)中得到對關係維護的列,這裡就指明的是外鍵customer_id;
4,javaType:代表完成映射後應該返回的物件類型,這裡肯定就是Orders
5,select:是這種映射方式的最關鍵的地方,這個select指代著cd.itcast.mybatis.customer.get這條SQL,這條sql即是得到customer的sql,而這條sql需要傳入一個customer的id,我們前面已經通過column="customer_id"指定了應該傳給cd.itcast.mybatis.customer.get的值就是結果集中的customer_id。
所以,可以看出,mybatis在這種映射的方式為:首先執行SELECT * FROM orders WHERE o.id = #{id},然後對id,sn,price屬性直接從結果集中得到;接著,從結果集中得到customer_id列對應的值,這個值即是orders對應customer對象的id,然後把這個值作為id交給cd.itcast.mybatis.customer.get去執行,而這個執行的結果會被拼裝為一個Customer對象,接著這個對象再會被設定到orders的customer屬性中。完成映射。
再次執行get測試:
DEBUG [main] - ooo Using Connection [[email protected]]
DEBUG [main] - ==> Preparing: SELECT * FROM ORDERS WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, sn, cust_id
TRACE [main] - <== Row: 1, 001, 1
DEBUG [main] - ooo Using Connection [[email protected]]
DEBUG [main] - ==> Preparing: SELECT * FROM CUSTOMER WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, name
TRACE [main] - <== Row: 1, c
Orders [id=1, sn=001]
Customer [id=1, name=c]
可以看到,正常查詢得到結果。但是,可能會有點疑問,我們之前在學hibernate的時候,不是說消極式載入是一種比較好的處理對象的方式嗎?但是現在我們看到例子中的對象並沒有消極式載入。因為預設情況下,mybatis不會消極式載入對象:
要開啟消極式載入,需要給mybatis增加幾個配置,在mybatis-config.xml中:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
再次執行get測試:得到的結果和剛才一樣,仍然感覺沒有執行消極式載入。問題在哪裡呢?其實mybatis已經完成了消極式載入,只是mybatis的消極式載入策略和hibernate的消極式載入策略不一樣。Hibernate是把被載入的目標對象做成proxy對象提供消極式載入(即這裡的Customer),而mybatis是把對象本身做成消極式載入對象,我們把get測試修改一下:
SqlSession session = MyBatisUtil.getInstance().openSession();
OrdersMapper om=session.getMapper(OrdersMapper.class);
Orders o=om.get(1l);
System.out.println(o.getClass());
session.close();
列印結果:
DEBUG [main] - ooo Using Connection [[email protected]]
DEBUG [main] - ==> Preparing: SELECT * FROM ORDERS WHERE id = ?
DEBUG [main] - ==> Parameters: 1(Long)
TRACE [main] - <== Columns: id, sn, cust_id
TRACE [main] - <== Row: 1, 001, 1
class cd.itcast.mybatis.domain.Orders$$EnhancerByCGLIB$$39a00050
001
可以看到,orders對象卻是是proxy對象,並且訪問其他屬性,不會發送sql。但是,只要得到customer,那麼查詢customer的sql就會立刻發送。簡單來說,mybatis的消極式載入原則是,只要你去拿這個對象,表明你在用這個對象。注意和hibernate的區別。
第二種方式,也是mybatis推薦的方式。上面那種方式,容易產生N+1問題。所以,mybatis建議使用內聯的映射方式。修改ordersmapping:
<resultMap type="Orders" id="ordersmapper">
<id property="id" column="id"/>
<result property="sn" column="sn"/>
<association property="customer" javaType="Customer">
<id property="id" column="cid"/>
<result property="name" column="cname"/>
</association>
</resultMap>
<select id="get" resultMap="ordersmapper" parameterType="long">
SELECT c.id as cid,c.name as cname,o.* FROM
ORDERS o LEFT JOIN CUSTOMER c ON o.CUST_ID = o.id WHERE o.id = #{id}
</select>
第一,在select中使用left join的方式把orders和對應的customer一起查詢出來,並且為customer設定對應的別名。
第二,修改resultMap,去掉select和column,而修改為內部的映射形式。
那麼,當mybatis執行完成sql之後,會把響應的屬性轉換成Orders對象的屬性,當解析到customer屬性時,mybatis會使用內部的映射關係把結果集中對應的列轉換成Customer對象。
再次運行get,執行結果相同。
再次說明,mybatis建議使用第二種方式來完成映射。有人又會考慮到,那麼如果只是orders的下拉式清單,在結果裡面不需要customer,這條sql不是很浪費效能嗎?這裡又要說到mybatis的另一個原則,按需配置。意思是,如果只是用作下拉式清單,那麼只需要再做一個select來專門為orders的下拉式清單做篩選。