本文作者:孔維勝,叩丁狼高級講師辽装。原創(chuàng)文章帮碰,轉(zhuǎn)載請注明出處。
MyBatis中通過package標(biāo)簽加載mapper映射文件的方式分析
看文章前的要求
在學(xué)習(xí)MyBatis的初級篇之前拾积,有兩個前提要求殉挽,第一.必須學(xué)會使用IDEA丰涉,因為在文章中,使用的工具為IDEA斯碌,文章中的案例也都是基于IDEA的一死。第二.必須學(xué)會使用MAVEN,因為在案例中需要的jar包傻唾,都是通過MAVEN來管理的投慈。
文章中的案例的開發(fā)環(huán)境
JDK 1.8
IDEA 2017.3
MySQL 5.1.38
Apache Maven 3.5.0
Tomcat 9.0.6
MyBatis 3.4.6
案例需要的表和數(shù)據(jù)
我們使用MyBatis的目的最終是訪問數(shù)據(jù)庫,所以在數(shù)據(jù)庫方面冠骄,我們先創(chuàng)建相應(yīng)的數(shù)據(jù)庫伪煤,表,導(dǎo)入相關(guān)的數(shù)據(jù)凛辣。如:
1.創(chuàng)建mybatis數(shù)據(jù)庫抱既。
2.在mybatis數(shù)據(jù)庫中創(chuàng)建department(部門表)。
DROP TABLE IF EXISTS `department`;
CREATE TABLE `department` (
`id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '部門ID',
`name` varchar(20) DEFAULT NULL COMMENT '部門名稱',
`sn` varchar(20) DEFAULT NULL COMMENT '部門縮寫',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
3.準(zhǔn)備department(部門表相關(guān)的數(shù)據(jù))
INSERT INTO `department` VALUES (1, '人力資源', 'HR_DEPT');
INSERT INTO `department` VALUES (2, '銷售部', 'SALE_DEPT');
INSERT INTO `department` VALUES (3, '開發(fā)部', 'DEVELOP_DEPT');
INSERT INTO `department` VALUES (4, '財務(wù)部', 'FINANCE_DEPT');
案例需求
需求:使用Mapper動態(tài)代理的方式完成所有數(shù)據(jù)的查詢操作扁誓。
需求分析
- 導(dǎo)入相關(guān)jar依賴
要使用MyBatis框架防泵,首先需要導(dǎo)入mybatis的核心包,MyBatis主要是操作數(shù)據(jù)庫跋理,替換掉傳統(tǒng)的JDBC方式訪問數(shù)據(jù)庫择克,所以需要導(dǎo)入mysql的驅(qū)動包。我們要在項目中使用單元測試進行測試前普,所以需要導(dǎo)入junit包肚邢,我們不想寫javaBean的setter和getter方法,可以導(dǎo)入lombok的包拭卿。
- 2.添加配置文件骡湖。
我們使用MyBatis框架,需要兩個配置文件峻厚,一個是MyBatis的主配置文件响蕴,主要用來配置事務(wù)管理器和數(shù)據(jù)庫的連接信息,一個是封裝SQL語句Mapper映射文件惠桃。我們?yōu)榱藬?shù)據(jù)庫的連接信息不寫死在主配置文件中浦夷,所以我們采用抽取的方式,把連接數(shù)據(jù)庫的信息抽取到db.properties文件中辜王,進行管理劈狐。通過package掃描的方式在主配置文件中掛載mapper的文件。如:
<package name="cn.wolfcode.mapper"/>
- 3.添加實體類和接口呐馆。
可能查詢數(shù)據(jù)需要查詢條件有很多肥缔,查詢數(shù)據(jù)需要封裝到對象中,所以我們可以定義一個JavaBean汹来,來封裝條件和查詢的數(shù)據(jù)续膳。
定義一個接口改艇,編寫操作數(shù)據(jù)庫方法。方法的名字保持和sql映射文件中的標(biāo)簽的id一一對應(yīng)坟岔。
- 4.增加工具類谒兄。
通過加載主配置文件來獲取SqlSessionFactory工廠對象,一般工廠對象都是單例模式的炮车,所以這個操作只需要做一次即可舵变。比如:我們不能每吃一次飯,都去建一所餐廳瘦穆。兩者的道理是一樣的纪隙。
而在MyBatis的官網(wǎng)給出的建議是SqlSessionFactory 一旦被創(chuàng)建就應(yīng)該在應(yīng)用的運行期間一直存在,沒有任何理由對它進行清除或重建扛或。因此 SqlSessionFactory 應(yīng)該使用其單例模式绵咱,只創(chuàng)建一次在整個應(yīng)用中,都可以使用熙兔。
工廠對象的獲取思考:
那么把獲取工廠對象的操作放在哪里合適呢悲伶?如果在本類中進行抽取,放在一個方法中住涉,但是每個DAO的實現(xiàn)類都這樣處理麸锉,還是會出現(xiàn)代碼的冗余。所以最合適的方式定義一個MyBatisUtil工具類舆声,把獲取工廠對象的操作抽取到工具類中花沉,那么工廠對象的獲取只需要獲取一次即可,所以在工具類中媳握,定義在哪里碱屁,只會執(zhí)行一次呢?靜態(tài)代碼塊蛾找,我們都知道娩脾,類中的靜態(tài)代碼塊,只會隨著類的加載而加載打毛,并且只執(zhí)行一次柿赊。
MyBatis工具類設(shè)計思考:
何為工具類,一般我們在定義的工具類的時候幻枉,希望使用者只使用而不要修改此類碰声,所以我們會設(shè)置這個類使用final進行修飾,這樣這個類就是終結(jié)類展辞,不能被繼承。一般工具類不會讓使用者去創(chuàng)建對象万牺,而是采用提供靜態(tài)方法的方式共使用者調(diào)用罗珍。
SqlSession對象獲取思考:
定義一個方法供外部訪問洽腺,獲取SqlSession對象。這個方法設(shè)計成靜態(tài)的這樣覆旱,調(diào)用方法的時候不用再創(chuàng)建工具類對象蘸朋。
- 5.添加測試類。
定義一個測試類扣唱,編寫一個測試方法藕坯,通過調(diào)用工具類中的方法獲取SqlSession對象,通過SqlSession對象調(diào)用getMapper方法獲取對應(yīng)的Mapper的代理對象噪沙,然后調(diào)用接口中的方法獲取所有數(shù)據(jù)炼彪。
案例代碼
pom.xml:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties"/>
<typeAliases>
<package name="cn.wolfcode.domain"/>
</typeAliases>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driverName}"/>
<property name="url" value="${url}"/>
<property name="username" value="${userName}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="cn.wolfcode.mapper"/>
</mappers>
</configuration>
db.properties:
driverName=com.mysql.jdbc.Driver
url=jdbc:mysql:///mybatis
userName=root
password=root123
DepartmentMapper.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="cn.wolfcode.mapper.DepartmetMapper">
<!--
select 表示查詢語句的標(biāo)簽。標(biāo)簽體的內(nèi)容即是查詢的SQL語句
id:SQL語句的唯一標(biāo)識
parameterType:傳入這條SQL語句的參數(shù)的類的完全限定名或別名正歼,
因為 MyBatis 可以通過 TypeHandler 推斷出具體傳入語句的參數(shù)辐马,故可以省略
resultType:返回期望的類型(類的完全限定名或別名),用來接收查詢的結(jié)果局义。
-->
<select id="selectAll" resultType="cn.wolfcode.domain.Department">
SELECT id,name,sn FROM department
WHERE id = #{id}
</select>
</mapper>
Department:
@Getter
@Setter
@ToString
public class Department {
// 主鍵id
private Long id;
// 部門名稱
private String name;
// 部門簡寫
private String sn;
}
DepartmentMapper:
public interface DepartmentMapper {
/**
* 查詢所有部門信息
* @return 返回所有部門信息的集合
*/
List<Department> selectAll();
}
MyBatisUtil:
public final class MyBatisUtil {
private static SqlSessionFactory factory = null;
static {
// 使用static靜態(tài)代碼塊喜爷,隨著類的加載而加載,只執(zhí)行一次
try {
// 加載MyBatis的主配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通過構(gòu)建器(SqlSessionFactoryBuilder)構(gòu)建一個SqlSessionFactory工廠對象
factory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (Exception e) {
e.printStackTrace();
}
}
// 獲取sqlSession對象
public static SqlSession openSession() {
return factory.openSession();
}
}
DepartmentMapperTest:
public class DepartmentMapperTest {
@Test
public void testQueryOne(){
// 獲取sqlSession對象
SqlSession sqlSession = MyBatisUtil.openSession();
// 獲取Mapper對象
DepartmentMapper departmentMapper = sqlSession.getMapper(DepartmentMapper.class);
List<Department> departmentList = departmentMapper.selectAll();
// 關(guān)閉資源
sqlSession.close();
// 遍歷結(jié)果
departmentList.stream().forEach(System.out::println);
}
DepartmentMapper文件加載流程分析
- 1 . 加載主配置文件萄唇,通過build方法構(gòu)建工廠對象檩帐。如:
-
2 . 創(chuàng)建XML配置構(gòu)建器的對象(XMLConfigBuilder)。底層使用的是XPath解析器另萤。 在這個方法的finally塊中湃密,對外部傳入的流,進行了關(guān)閉仲墨。所以外部不需要進行關(guān)閉了勾缭。如:
-
3 . 通過構(gòu)建器對象調(diào)用parse方法,把解析的數(shù)據(jù)封裝到Configuration對象中目养。我們主要是關(guān)心mapper文件的加載俩由,所以繼續(xù)往下看。如:
- 4 .方法中定義了一個while死循環(huán)癌蚁,主要是便利mappers節(jié)點下面的所有元素幻梯。因為我們采用的是在主配置文件中使用package掃描的方式掛載的mapper映射文件。所以跳入if代碼塊努释。在if塊中通過獲取name屬性的值碘梢,拿到了mapper文件的所屬的包名,通過configuration對象調(diào)用addMappers方法把mapper映射文件所在的包傳入伐蒂。如:
- 5 .調(diào)用Mapper注冊對象中(MapperRegistry)的addMappers方法煞躬,添加映射。如:
- 6 .創(chuàng)建ResolverUtil工具類,通過調(diào)用find方法把包下面的字節(jié)碼對象找出來恩沛,并存入到Set集合中在扰,通過調(diào)用getClasses方法取出,進行遍歷雷客。把每一個字節(jié)碼對象傳入addMapper方法芒珠。如:
- 7 .在MapperRegistry(映射注冊類)中定義一個map容器(knowMappers),用來存入映射搅裙。在addMapper方法中皱卓,先通過調(diào)用isInterface方法看看mapper是不是接口,必須是接口部逮,才會添加娜汁。在通過調(diào)用hasMapper方法來判斷是否已經(jīng)添加過了,如果已經(jīng)添加甥啄,就拋出一個綁定異常存炮。通過標(biāo)記loadCompleted,來確保添加成功蜈漓。如果添加出現(xiàn)了異常穆桂,在finally塊中刪除map中存入的映射。把字節(jié)碼對象作為key融虽,創(chuàng)建該字節(jié)碼對象的代理對象作為value享完,存入knowMappers中。并創(chuàng)建MapperAnnotationBuilder對象如:
- 8 .MapperAnnotationBuilder這個類總會優(yōu)先解析xml配置文件有额,并且這個xml配置文件必須與Class對象所在的包路徑一致般又,且文件名要與類名一致。在解析完xml配置文件后巍佑,才會開始解析Class對象中包含的注解茴迁。里面有個if判斷,如果在主配置對象(configuration)添加過接口標(biāo)記萤衰,表示解析過堕义,就不再進入if語句。首先調(diào)用loadXmlResource方法脆栋,解析指定的xml配置文件倦卖。如:
- 9 . 在這個方法中,先通過if判斷之前是否解析過椿争,如果沒有解析過怕膛,則進入if語句,把包名中的"."替換成"/"秦踪,這樣變成了文件夾褐捻,然后在后面追加".xml"后綴掸茅。這樣拼接成一個xml文件的資源路徑。然后加載到內(nèi)存柠逞。在通過調(diào)用parse方法進行解析xml文件倦蚪。
所以這也是為何如果使用package掃描的方式,必須要保證接口和mapper映射文件必須在同一個包中边苹,名字也必須相同的原因。如:
- 10 . 繼續(xù)往下解析裁僧。如:
DepartmentMapper文件加載整體流程圖
想獲取更多技術(shù)干貨个束,請前往叩丁狼官網(wǎng):http://www.wolfcode.cn/all_article.html