![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5yMwATOwEDN3kDM3MDMlZmN0QDOyEjMzYDZhdTYiZzM38CX0JXZ252bj91Ztl2Lc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
二、MyBatis關聯映射
#####1、主鍵映射
######1.1、主鍵映射作用
- 當資料插入操作不關心插入後資料的主鍵(唯一辨別),那麼建議使用 不傳回自增主鍵值 的方式來配置插入語句,這樣可以避免額外的SQL開銷.
-
當執行插入操作後需要立即擷取插入的自增主鍵值,比如一次操作中儲存一對多這種關系的資料,那麼就要使用 插入後擷取自增主鍵值 的方式配置.
mybatis進行插入操作時,如果表的主鍵是自增的,針對不同的資料庫相應的操作也不同。基本上經常會遇到的就是Oracle Sequece 和 Mysql 自增主鍵,解釋如下。
######1.2、自動遞增
一對多的那種表結構,在插入多端資料時,需要擷取剛剛儲存了的一段的主鍵。那麼這個時候,上述的配置就無法滿足需要了。為此我們需要使用mybatis提供的
<selectKey />
來單獨配置針對自增逐漸的處理。
######1.2.1、Oracle Sequence 配置
<sql id='TABLE_NAME'>TEST_USER</sql> <sql id='TABLE_SEQUENCE'>SEQ_TEST_USER_ID.nextval</sql>
<!-- 注意這裡需要先查詢自增主鍵值 --> <insert id="insert" parameterType="User"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> select <include refid="TABLE_SEQUENCE" /> from dual </selectKey> insert into <include refid="TABLE_NAME" /> (ID,NAME,AGE) values ( #{id}, #{name}, #{age} ) </insert>
當使用了
<selectKey />
後,在實際的插入操作時,mybatis會執行以下兩句SQL:
select SEQ_TEST_USER_ID.nextval from dual; // 語句1
insert into (ID,NAME,AGE) values ( ?, ?, ? ); // 語句2
在執行插入 語句2 之前,會先執行 語句1 以擷取目前的ID值,然後mybatis使用反射調用
User
對象的
setId
方法,将 語句1 查詢出的值儲存在
User
對象中,然後才執行 語句2 這樣就保證了執行完插入後
User user = new User(); user.setName("test"); user.setAge(24);
userMapper.insert(user); System.out.println(user.id); // user.id 不為空
user.id`是有值的。
######1.2.2、Mysql自增主鍵配置
針對于Mysql這種自己維護主鍵的資料庫,可以直接使用以下配置在插入後擷取插入主鍵,
<sql id='TABLE_NAME'>TEST_USER</sql>
<insert id="insert" useGeneratedKeys="true" keyProperty="id" parameterType="User"> insert into <include refid="TABLE_NAME" /> ( NAME, AGE ) values ( #{name}, #{age} ) </insert>
當然,由于Mysql的自增主鍵可以通過SQL語句
select LAST_INSERT_ID();
來擷取的。是以針對Mysql,Mybatis也可配置如下:
<sql id='TABLE_NAME'>TEST_USER</sql>
<!-- 注意這裡需要先查詢自增主鍵值 --> <insert id="insert" parameterType="User"> <selectKey keyProperty="id" resultType="int" order="BEFORE"> SELECT LAST_INSERT_ID() </selectKey> insert into <include refid="TABLE_NAME" /> (ID,NAME,AGE) values ( #{id}, #{name}, #{age} ) </insert>
隻不過該中配置需要額外的一條查詢SQL!
######1.3、非自動遞增
如果考慮到插入資料的主鍵不作為其他表插入資料的外鍵使用,那麼可以考慮使用這種方式。
######1.3.1、Oracle Sequence 配置
<sql id='TABLE_NAME'>TEST_USER</sql> <sql id='TABLE_SEQUENCE'>SEQ_TEST_USER_ID.nextval</sql>
<!-- 注意這裡直接調用sequence的nextval函數 --> <insert id="insert" parameterType="User"> insert into <include refid="TABLE_NAME" /> (ID,NAME,AGE) values ( <include refid="TABLE_SEQUENCE" /> ,#{name}, #{age} ) </insert>
當插入語句如上配置時,那麼針對如下語句
User user = new User(); user.setName("test"); user.setAge(24);
userMapper.insert(user); System.out.println(user.id); // user.id 為空
user.id
為空,也就是說如上的配置并不能在完成插入操作後将插入時的主鍵值存放到儲存的對象中。
######1.3.2、Mysql自增主鍵配置
由于mysql資料庫中,可以設定表的主鍵為自增,是以對于Mysql資料庫在mybatis配置插入語句時,不指定插入ID字段即可。主鍵的自增交由Mysql來管理。
<sql id='TABLE_NAME'>TEST_USER</sql>
<!-- 注意這裡的插入SQL中是沒有指明ID字段的! --> <insert id="insert" parameterType="User"> insert into <include refid="TABLE_NAME" /> (NAME,AGE) values (#{name}, #{age} ) </insert>
同樣,針對Mysql如此配置mybaits,插入完成後
user.id
為空
#####2、關聯映射
######2.1、關聯映射作用
在現實的項目中進行資料庫模組化時,我們要遵循資料庫設計範式的要求,會對現實中的業務模型進行拆分,封裝在不同的資料表中,表與表之間存在着
一對多或是
多對多的對應關系。進而,我們對資料庫的增删改查操作的主體,也就從單表變成了多表。那麼Mybatis中是如何實作這種多表關系的映射呢?
查詢結果集ResultMap
resultMap 元素是 MyBatis 中最重要最強大的元素。它就是讓你遠離 90%的需要從結果 集中取出資料的 JDBC 代碼的那個東西,而且在一些情形下允許你做一些 JDBC 不支援的事 情。 事實上, 編寫相似于對複雜語句聯合映射這些等同的代碼,也許可以跨過上千行的代碼。
有朋友會問,之前的示例中我們沒有用到結果集,不是也可以正确地将資料表中的資料映射到Java對象的屬性中嗎?是的。這正是resultMap元素設計的初衷,就是簡單語句不需要明确的結果映射,而很多複雜語句确實需要描述它們的關系。
- resultMap元素中,允許有以下直接子元素:
- constructor - 類在執行個體化時,用來注入結果到構造方法中(本文中暫不講解)
- id - 作用與result相同,同時可以辨別出用這個字段值可以區分其他對象執行個體。可以了解為資料表中的主鍵,可以定位資料表中唯一一筆記錄
- result - 将資料表中的字段注入到Java對象屬性中
- association - 關聯,簡單的講,就是“有一個”關系,如“使用者”有一個“帳号”
- collection - 集合,顧名思議,就是“有很多”關系,如“客戶”有很多“訂單”
-
discriminator - 使用結果集決定使用哪個個結果映射(暫不涉及)
每個元素的用法及屬性我會在下面結合使用進行講解。
我們在資料庫中額外建立三張資料表,分别表示銷售人員、客戶,以及銷售和客戶多對多的對應關系。每個銷售、客戶都有一個登入帳号。
CREATE TABLE `customer` (
`customer_id` int(10) NOT NULL AUTO_INCREMENT,
`customer_name` varchar(200) NOT NULL,
`user_id` int(10) DEFAULT NULL,
`is_valid` tinyint(4) NOT NULL DEFAULT '1',
`created_time` datetime NOT NULL,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`customer_id`),
KEY `customer_name` (`customer_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `salesman` (
`sales_id` int(10) NOT NULL AUTO_INCREMENT,
`sales_name` varchar(64) NOT NULL,
`sales_phone` varchar(32) DEFAULT NULL,
`sales_fax` varchar(32) DEFAULT NULL,
`sales_email` varchar(100) DEFAULT NULL,
`user_id` int(10) DEFAULT NULL,
`report_to` int(10) DEFAULT '0',
`is_valid` tinyint(4) NOT NULL DEFAULT '1',
`created_time` datetime DEFAULT NULL,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`sales_id`),
KEY `sales_name` (`sales_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `customer_sales` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`customer_id` int(10) NOT NULL,
`sales_id` int(10) NOT NULL,
`created_time` datetime NOT NULL,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `customer_id` (`customer_id`,`sales_id`) USING BTREE,
KEY `sales_id` (`sales_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
實作銷售與登入使用者一對一關系
這裡采用Mybatis的接口式程式設計。無論是對單表進行映射,還是對多表映射,步驟都是相同的,唯一的不同就在映射檔案的編寫上。
首先,我們需要銷售建立一個Java類,其中的userInfo屬性對應銷售的登入使用者資訊的。
public class Sales {
private int salesId;
private String salesName;
private String phone;
private String fax;
private String email;
private int isValid;
private Timestamp createdTime;
private Timestamp updateTime;
private User userInfo;
第二步,編寫Mybatis映射檔案,需要注意的是映射檔案的名稱空間,要與我們編寫的接品的全限定名一緻(包名+接口名)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.dao.ISalesDao">
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<id property="salesId" column="sales_id" />
<result property="salesName" column="sales_name" />
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
<!-- 定義多對一關聯資訊(每個銷售人員對應一個登入帳号) -->
<association property="userInfo" column="user_id" javaType="User" select="selectUser">
<id property="userId" column="userId" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
</association>
</resultMap>
<select id="selectUser" resultType="User">
SELECT user_id, user_name, user_password, nick_name, email, is_valid, created_time
FROM sys_user WHERE user_id = #{id}
</select>
<select id="getById" parameterType="int" resultMap="salesResultMap" >
SELECT sales_id, sales_name, sales_phone, sales_fax, sales_email, user_id, is_valid, created_time, update_time
FROM salesman WHERE sales_id=#{id}
</select>
</mapper>
第三步,将映射檔案注冊到Mybatis中。
<mappers>
<mapper resource="com/qf/mapping/User.xml" />
<mapper resource="com/qf/mapping/Sales.xml" />
</mappers>
第四步,編寫接口
public interface ISalesDao {
public Sales getById(int id);
}
第五步,編寫測試用例
public class SalesDaoTest {
private Reader reader;
private SqlSessionFactory sqlSessionFactory;
@Before
public void setUp() throws Exception {
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
@After
public void tearDown() throws Exception {
}
@Test
public void getById() {
SqlSession session = sqlSessionFactory.openSession();
try {
ISalesDao sd = session.getMapper(ISalesDao.class);
Sales sales = sd.getById(2);
assertNotNull(sales);
System.out.println(sales);
} finally {
session.close();
}
}
}
下面我們就針對第二步,映射檔案中的resultMap編寫進行詳細講解。
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<id property="salesId" column="sales_id" />
<result property="salesName" column="sales_name" />
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="createdTime" />
<result property="updateTime" column="update_time" />
<!-- 定義多對一關聯資訊(每個銷售人員對應一個登入帳号) -->
<association property="userInfo" column="user_id" javaType="User" select="selectUser">
<id property="userId" column="userId" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
</association>
</resultMap>
和其他元素一樣,我們都需要為其取一個唯一的id,并指定其在Java中對應的類型,由于我沒有在Mybatis配置檔案中為Sales類指定别名,是以這裡使用的是全限定名。
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
使用id和result元素指定資料表中字段與Java類中屬性的映射關系,除了我phone、fax和email三行映射代碼,其餘的全部可以省去不寫。為什麼?這個就像前面示例中使用到的User類一樣,Mybatis會自動幫助我們完成映射工作,不需要我們額外編寫代碼。那麼為什麼phone、fax和email這三個字段的映射關系不能省略呢?這是因為我在編寫Sales類的時候埋下了伏筆,我故意不按照按駝峰規則對這三個屬性進行命名,同時也不與資料表中的字段名相同,為了確定可以正确的将字段映射到屬性上,我們必須手工編寫映射在代碼,明确地告訴Mybatis我們的映射規則。
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
</resultMap>
下面重點來了,association元素來幫助我們完成銷售與登入使用者對應關系的映射。她實作了“有一個”的關系映射,我們需要做的隻是告訴Mybatis,這個關系是通過哪一個字段來建立關聯的,被關聯的對象類型是什麼,以及将關聯對象映射到哪個屬性上面。如果被關聯對象的資料結構比較簡單,就如本文中的登入使用者表這樣,那麼可以有更簡單的寫法。
<association property="userInfo" column="user_id" javaType="User" select="selectUser" />
我們還需要告訴Mybatis,加載關聯的方式。MyBatis 在這方面會有兩種不同的方式:
- 嵌套查詢:通過執行另外一個 SQL 映射語句來傳回預期的複雜類型。
- 嵌套結果:使用嵌套結果映射來處理重複的聯合結果的子集。
######2.2、嵌套查詢映射
我們在這裡先使用嵌套查詢來實作。使用屬性select指定了關聯資料的查詢語句。
<select id="selectUser" resultType="User">
SELECT user_id, user_name, user_password, nick_name, email, is_valid, created_time
FROM sys_user WHERE user_id = #{id}
</select>
當對Sales進行映射的時候,Mybatis會使用這個名為selectUser的查詢語句去擷取相關聯的資料資訊。這種方法使用起來很簡單。但是簡單,不代表最好。對于大型資料集合和清單這種方式将會有性能上的問題,就是我們熟知的 “N+1 查詢問題”。概括地講,N+1 查詢問題可以是這樣引起的:
- 你執行了一個單獨的 SQL 語句來擷取結果清單(就是“+1”)。
- 對傳回的每條記錄,你執行了一個查詢語句來為每個加載細節(就是“N”)。
這個問題會導緻成百上千的 SQL 語句被執行。這通常不是期望的。
MyBatis 能延遲加載這樣的查詢就是一個好處,是以你可以分散這些語句同時運作的消耗。然而,如果你加載一個清單,之後迅速疊代來通路嵌套的資料,你會調用所有的延遲加載,這樣的行為可能是很糟糕的。
######2.3、嵌套結果映射
下面我們就來講一下另一種實式方式:嵌套結果。使用這種方式,就可以有效地避免了N+1問題。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.dao.ISalesDao">
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<id property="salesId" column="sales_id" />
<result property="salesName" column="sales_name" />
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP" />
<!-- 定義多對一關聯資訊(嵌套結果方式) -->
<association property="userInfo" resultMap="userResult" />
</resultMap>
<resultMap id="userResult" type="User">
<id property="userId" column="user_id" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="nickName" column="nick_name" />
<result property="email" column="user_email" />
<result property="isValid" column="user_is_valid" />
<result property="createdTime" column="user_created_time" />
<result property="updateTime" column="user_update_time" />
</resultMap>
<select id="getById" parameterType="int" resultMap="salesResultMap">
SELECT
sales_id, sales_name, sales_phone, sales_fax, sales_email,
salesman.is_valid, salesman.created_time, salesman.update_time,
sys_user.user_id as user_id, user_name, user_password, nick_name,
email as user_email,
sys_user.is_valid as user_is_valid, sys_user.created_time as
user_created_time,
sys_user.update_time as user_update_time
FROM
salesman left outer join sys_user using(user_id)
WHERE sales_id=#{id}
</select>
</mapper>
和嵌套查詢相比,使用嵌套結果方式,在映射檔案上主要有以下三處修改:
一、修改association元素,無需指定column,另外将resultType改為使用resultMap。為什麼?這是因為後面我們會把select語句改為多表關聯查詢,這樣就會有些字段名是沖突的,我們不得不使用别名。這一點對于Mybatis而言,就相當于字段名發生了變化,那麼就需要我們手工來維護映射關系。另外,我們也無需指定javaType屬性了,因為在resultMap中,已經指定了對應的Java實體類,這裡就可以省略了。
<association property="userInfo" resultMap="userResult" />
二、為關聯結果集編寫映射關系,大家可以看到,好多字段名稱已經發生了變化,如is_valid這個字段由于salesman和sys_user表中都存在這個字段,是以我們不得不為其起了一個别名user_is_valid。
<resultMap id="userResult" type="User">
<id property="userId" column="user_id" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="nickName" column="nick_name" />
<result property="email" column="user_email" />
<result property="isValid" column="user_is_valid" />
<result property="createdTime" column="user_created_time" />
<result property="updateTime" column="user_update_time" />
</resultMap>
三、修改查詢語句,由單表查詢改表多表關聯查詢
<select id="getById" parameterType="int" resultMap="salesResultMap">
SELECT sales_id, sales_name, sales_phone, sales_fax, sales_email,
salesman.is_valid, salesman.created_time, salesman.update_time,
sys_user.user_id as user_id, user_name, user_password, nick_name,
email as user_email,
sys_user.is_valid as user_is_valid, sys_user.created_time as
user_created_time,
sys_user.update_time as user_update_time
FROM salesman left outer join sys_user using(user_id)
WHERE sales_id=#{id}
</select>
至此,關聯映射已講解完了。還有集合映射沒有講,哇咔咔,内空實在是太多了〜〜〜〜今晚通宵也未必能寫得完了。暫時先寫到這兒吧,下回再繼續講解如何實作多對多的集合映射。
#####3、集合映射
######3.1、集合映射作用
集合映射,實作銷售與客戶的多對多關系
第一步,在動手編寫映射檔案之前,我們需要對Sales類增加一個List屬性,用以儲存銷售員對應的客戶清單。
private List<Customer> customers;
public Sales() {
super();
this.setCustomers(new ArrayList<Customer>());
}
public List<Customer> getCustomers() {
return customers;
}
protected void setCustomers(List<Customer> customers) {
this.customers = customers;
}
同時增加一個客戶類。
public class Customer {
private int customerId;
private String customerName;
private int isValid;
private Timestamp createdTime;
private Timestamp updateTime;
private User userInfo;
第二步,修改映射檔案。我們先使用嵌套查詢方式來實作為銷售加載客戶清單。首先在resultMap中增加客戶集合映射的定義。
######3.2、嵌套查詢映射
<!-- 定義一對多集合資訊(每個銷售人員對應多個客戶) -->
<collection property="customers" javaType="ArrayList" column="sales_id" ofType="Customer" select="getCustomerForSales" />
集合映射的定義與關聯映射定義很相似,除了關鍵字不同外,還多了兩個屬性JavaType和ofType。
property用于指定在Java實體類是儲存集合關系的屬性名稱
JavaType用于指定在Java實體類中使用什麼類型來儲存集合資料,多數情況下這個屬性可以省略的。
column用于指定資料表中的外鍵字段名稱。
ofType用于指定集合中包含的類型。
select用于指定查詢語句。
然後再定義查詢客戶的查詢語句。
<select id="getCustomerForSales" resultType="com.qf.pojo.Customer">
SELECT c.customer_id, c.customer_name, c.user_id, c.is_valid,
c.created_time, c.update_time
FROM customer c INNER JOIN customer_sales s USING(customer_id)
WHERE s.sales_id = #{id}
</select>
需要注意的是,無論是關聯還是集合,在嵌套查詢的時候,查詢語句的定義都不需要使用parameterType屬性定義傳入的參數類型,因為通常作為外鍵的,都是簡單資料類型,查詢語句會自動使用定義在association或是collection元素上column屬性作為傳入參數的。
運作測試用例,看到如下結果就說明我們的映射檔案是正确的了。
######3.3、嵌套結果映射
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<id property="salesId" column="sales_id" />
<result property="salesName" column="sales_name" />
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
<!-- 定義多對一關聯資訊(嵌套結果方式) -->
<association property="userInfo" resultMap="userResult" />
<!-- 定義一對多集合資訊(每個銷售人員對應多個客戶) -->
<!-- <collection property="customers" column="sales_id" select="getCustomerForSales" /> -->
<collection property="customers" ofType="com.qf.pojo.Customer">
<id property="customerId" column="customer_id" />
<result property="customerName" column="customer_name" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
<!-- 映射客戶與登入使用者的關聯關系,請注意columnPrefix屬性 -->
<association property="userInfo" resultMap="userResult" columnPrefix="cu_" />
</collection>
</resultMap>
這裡将客戶的映射關系直接寫在了銷售的resultMap中。上述代碼與關聯映射十分相似,隻是有一點需要朋友們留心,那就是在對客戶資料進行映射的時候,
我們使用了association元素的一個新的屬性columnPrefix。這個屬性是做什麼用的呢?從名字上了解,就是給每個欄位之前加上字首。Bingo!答對了,那麼什麼情況下會使用到這個屬性呢?後面我們會結合着修改後的查詢語句來說明這個屬性的使用場景。請耐心的往下看。:)
映射結果修改好了,緊接着我們就要修改查詢語句了。
<select id="getById" parameterType="int" resultMap="salesResultMap">
SELECT
s.sales_id, s.sales_name, s.sales_phone, s.sales_fax, s.sales_email,
s.is_valid, s.created_time, s.update_time,
su.user_id as user_id, su.user_name, su.user_password, su.nick_name,
su.email as user_email,
su.is_valid as user_is_valid,
su.created_time as user_created_time,
su.update_time as user_update_time,
c.customer_id, c.customer_name, c.is_valid as customer_is_valid,
c.created_time as customer_created_time,
c.update_time as customer_update_time,
cu.user_id as cu_user_id, cu.user_name as cu_user_name, cu.user_password as cu_user_password,
cu.nick_name as cu_nick_name, cu.email as cu_user_email, cu.is_valid as cu_user_is_valid,
cu.created_time as cu_user_created_time, cu.update_time as cu_user_update_time
FROM
salesman s LEFT OUTER JOIN sys_user su ON s.user_id = su.user_id
INNER JOIN customer_sales cs USING(sales_id)
LEFT OUTER JOIN customer c USING(customer_id)
LEFT OUTER JOIN sys_user cu ON c.user_id = cu.user_id
WHERE sales_id=#{id}
</select>
這個語句乍看起來有些複雜,其實很容易了解。這裡用到了四張資料表,銷售、客戶、客房銷售關系表和登入使用者表。具體的字段我就不說了,主要說一下這個登入使用者表。這張資料表在查詢語句中出現了兩次,為什麼呢?因為銷售與登入使用者有關聯關系,同樣地,客戶也與登入使用者表有關聯關系,是以我們需要對使用者表進行兩次Join操作。
那麼問題來了,銷售要使用者有關聯,客戶也要與使用者有關聯,這種映射語句應該如何寫呢?難道要對使用者表寫兩次映射?聰明的朋友一定會說,我們可以複用之前寫過的使用者映射結果集呀!答案是肯定的。我們不妨在這裡再次貼出這段代碼,一起回憶一下。
<resultMap id="userResult" type="User">
<id property="userId" column="user_id" />
<result property="userName" column="user_name" />
<result property="userPassword" column="user_password" />
<result property="nickName" column="nick_name" />
<result property="email" column="user_email" />
<result property="isValid" column="user_is_valid" />
<result property="createdTime" column="user_created_time" />
<result property="updateTime" column="user_update_time" />
</resultMap>
資料表中的字段與Java實體類中的屬性的映射關系是一一對應的,Mybatis會根據我們定義的映射關系,将資料表中字段的映射到Java實體類屬性上。
可是我們的查詢語句中對使用者表進行了兩次Join操作,第一次是銷售與使用者的Join,第二次是客戶與使用者的Join。而SQL語句是不允許在同一條查詢語句中出現相同字段名的(雖然我們有時候會這樣寫,但是資料庫會自動幫我們為重名的字段名起個别名的,比如在字段名後添加數字)。如果我們為第二次Join進來的使用者表中的字段使用别名方式,那麼就會導緻映射的到客戶類中的使用者資訊缺失,因為字段名與我們在映射檔案中的定義不一緻。如何解決這個問題呢?這時候該columnPrefix屬性出場了。
Mybatis也考慮到這種情況的出現,她允許我們在重複出現的字段名前加上一個
統一的字元字首,這樣就可以有效的避免字段重名,又可以複用之前定義的映射結果集。
在上述的查詢語句中,我們為第二次Join進來的使用者表中的字段都加上了“cu”做為區分重名字段的字首,同時使用columnPrefix屬性告訴Mybatis在第二次對使用者表映射的時候,将字段名是以“cu”打頭的字段值映射到Java實體類屬性當中。這樣就可以正确的把客戶與使用者的關聯資訊映射到Customer對象當中了。
<association property="userInfo" resultMap="userResult" columnPrefix="cu_" />
我們之前在User.xml檔案中定義過使用者表的映射結果集,現在在Sales.xml中也需要使用到同樣的結果集,是否可以直接跨檔案引用呢?答案是肯定的了,不然對于同一個映射結果集,我們要多處編寫,多處維護,這樣不僅工作量大,對日後的維護也帶來了一定的麻煩。我們隻需要在引用處使用結果集的全限定名就可以了。
<resultMap id="salesResultMap" type="com.qf.pojo.Sales">
<id property="salesId" column="sales_id" />
<result property="salesName" column="sales_name" />
<result property="phone" column="sales_phone" />
<result property="fax" column="sales_fax" />
<result property="email" column="sales_email" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
<!-- 定義多對一關聯資訊(嵌套查詢方式) -->
<!-- <association property="userInfo" column="user_id" javaType="User"
select="selectUser" fetchType="lazy"> </association> -->
<!-- 定義多對一關聯資訊(嵌套結果方式) -->
<association property="userInfo" resultMap="com.qf.xml.user.userResult" />
<!-- 定義一對多集合資訊(每個銷售人員對應多個客戶) -->
<!-- <collection property="customers" column="sales_id" select="getCustomerForSales"
/> -->
<collection property="customers" ofType="com.qf.pojo.Customer">
<id property="customerId" column="customer_id" />
<result property="customerName" column="customer_name" />
<result property="isValid" column="is_valid" />
<result property="createdTime" column="created_time" />
<result property="updateTime" column="update_time" />
<association property="userInfo" resultMap="com.qf.xml.user.userResult" columnPrefix="cu_" />
</collection>
</resultMap>
#####4、鑒别器
######4.1、鑒别器的作用
鑒别器在于确定使用那個ResultMap來映射SQL查詢語句,在實作中我們往往有一個基類,然後可以派生一些類。比如我們要選擇一群人可以用List<Person>,然而Person裡面有個性别sex,根據它還可以分為Male或者Female。鑒别器就要根據sex決定用Male還是用Female相關的Mapper進行映射。
這些話還是很抽象,不過說起鑒别器,語言真的不好用描述,不過不要緊,我們來看一個執行個體就豁然開朗了,我們知道在上篇中我們已經有了一個員工的POJO,然後繼承這個POJO分成一個男性,一個女性的POJO。
######4.2、鑒别器的使用
當我們查詢一批員工的時候,我們希望的是傳回一個List<Employee>,而裡面的元素根據性别(sex)自動比對是MaleEmployee或者是FemaleEmployee,于是我們需要根據sex的值去決定使用MaleEmployee或者是FemaleEmployee的resultMap去映射,這便是鑒别器。
讓我們來定義employ的mapper,xml代碼如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.qf.mapper.EmployeeMapper">
<resultMap id="employeeMap" type="com.qf.pojo.Employee">
<id property="id" column="id" />
<result property="empName" column="emp_name" />
<result property="sex" column="sex" />
<association property="employeeCard" column="id"
select="com.qf.mapper.EmployeeCardMapper.getEmployeeCardByEmpId" />
<collection property="projectList" column="id"
select="com.qf.mapper.ProjectMapper.findProjectByEmpId" />
<discriminator javaType="int" column="sex">
<case value="1" resultMap="maleEmployeeMap" />
<case value="2" resultMap="femaleEmployeeMap" />
</discriminator>
</resultMap>
<select id="getEmployee" parameterType="int" resultMap="employeeMap">
select id, emp_name as empName, sex from t_employee where id =#{id}
</select>
<resultMap id="maleEmployeeMap" type="com.qf.pojo.MaleEmployee" extends="employeeMap">
<collection property="prostateList" select="com.qf.mapper.MaleEmployeeMapper.findProstateList" column="id" />
</resultMap>
<resultMap id="femaleEmployeeMap" type="com.qf.pojo.FemaleEmployee" extends="employeeMap">
<collection property="uterusList" select="com.qf.mapper.FemaleEmployeeMapper.findUterusList" column="id" />
</resultMap>
</mapper>
我們這裡定義了employee的resultMap,它除了級聯其他的和平時我們定義的沒什麼不一樣。這裡先不看别的級聯,先看看鑒别器:<discriminator>元素,我們定義了用javaType說明它用的是整數作為參數,而column指的是SQL對應的列為sex。
那麼<case>定義的是你的條件分支:
當sex=1時候,采用maleEmployeeMap;
當sex=2時,采用femaleEmployeeMap。
maleEmployeeMap和femaleEmployeeMap都繼承了employeeMap,并且擴充了一個屬性,它們用select屬性,來定義如何取對應的屬性資料。要記住下面這句話,後面我們還将讨論它:*這裡使用了全限定路徑,其次用column="id"作為參數傳遞,如果是多個參數的,需要用逗号分隔。
3、關聯Mapper:上面我們看到了我們使用了select關聯其他的sql語句,而select裡面給的就是一個全限定的路徑。分别是:
com.qf.mapper.MaleEmployeeMapper.findProstateList
和
com.qf.mapper.FemaleEmployeeMapper.findUterusList
現在讓我們看看這兩個Mapper是怎麼樣的:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="comqf.mapper.MaleEmployeeMapper">
<select id="findProstateList" parameterType="int" resultType="string">
select prostate from t_healthy_male where emp_id = #{emp_id}
</select>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="comqf.mapper.FemaleEmployeeMapper">
<select id="findUterusList" parameterType="int" resultType="string">
select uterus from t_healthy_female where emp_id = #{emp_id}
</select>
</mapper>
顯然他們都比較簡單,和我們定義的普通Mapper沒什麼差別。