《深入理解mybatis原理(七)》 MyBatis的架構設計以及例項分析
MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實現卻比較簡單、優雅。本文主要講述MyBatis的架構設計思路,並且討論MyBatis的幾個核心部件,然後結合一個select查詢例項,深入程式碼,來探究MyBatis的實現。
一、MyBatis的框架設計
注:上圖很大程度上參考了iteye 上的chenjc_it 所寫的博文原理分析之二:框架整體設計 中的MyBatis架構體圖,chenjc_it總結的非常好,贊一個!
1.介面層---和資料庫互動的方式
MyBatis和資料庫的互動有兩種方式:
a.使用傳統的MyBatis提供的API;
b. 使用Mapper介面
1.1.使用傳統的MyBatis提供的API
這是傳統的傳遞Statement Id 和查詢引數給SqlSession 物件,使用SqlSession物件完成和資料庫的互動;MyBatis提供了非常方便和簡單的API,供使用者實現對資料庫的增刪改查資料操作,以及對資料庫連線資訊和MyBatis自身配置資訊的維護操作。
上述使用MyBatis的方法,是建立一個和資料庫打交道的SqlSession物件,然後根據Statement Id 和引數來操作資料庫,這種方式固然很簡單和實用,但是它不符合面嚮物件語言的概念和麵向介面程式設計的程式設計習慣。由於面向介面的程式設計是面向物件的大趨勢,MyBatis
為了適應這一趨勢,增加了第二種使用MyBatis支援介面(Interface)呼叫方式。1.2. 使用Mapper介面
MyBatis將配置檔案中的每一個<mapper>節點抽象為一個 Mapper 介面,而這個介面中宣告的方法和跟<mapper>節點中的<select|update|delete|insert>節點項對應,即<select|update|delete|insert>節點的id值為Mapper介面中的方法名稱,parameterType值表示Mapper對應方法的入參型別,而resultMap值則對應了Mapper
介面表示的返回值型別或者返回結果集的元素型別。
根據MyBatis的配置規範配置好後,通過SqlSession.getMapper(XXXMapper.class) 方法,MyBatis會根據相應的介面宣告的方法資訊,通過動態代理機制生成一個Mapper例項,我們使用Mapper介面的某一個方法時,MyBatis會根據這個方法的方法名和引數型別,確定Statement Id,底層還是通過SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject);等等來實現對資料庫的操作,(至於這裡的動態機制是怎樣實現的,我將準備專門一片文章來討論,敬請關注~)
MyBatis引用Mapper介面這種呼叫方式,純粹是為了滿足面向介面程式設計的需要。(其實還有一個原因是在於,面向介面的程式設計,使得使用者在介面上可以使用註解來配置SQL語句,這樣就可以脫離XML配置檔案,實現“0配置”)。
2.資料處理層
資料處理層可以說是MyBatis的核心,從大的方面上講,它要完成三個功能:
a. 通過傳入引數構建動態SQL語句;
b. SQL語句的執行以及封裝查詢結果整合List<E>2.1.引數對映和動態SQL語句生成
動態語句生成可以說是MyBatis框架非常優雅的一個設計,MyBatis通過傳入的引數值,使用Ognl 來動態地構造SQL語句,使得MyBatis有很強的靈活性和擴充套件性。
引數對映指的是對於Java資料型別和jdbc資料型別之間的轉換:這裡有包括兩個過程:查詢階段,我們要將java型別的資料,轉換成jdbc型別的資料,通過preparedStatement.setXXX()來設值;另一個就是對resultset查詢結果集的jdbcType資料轉換成java資料型別。
(至於具體的MyBatis是如何動態構建SQL語句的,我將準備專門一篇文章來討論,敬請關注~)
2.2. SQL語句的執行以及封裝查詢結果整合List<E>
動態SQL語句生成之後,MyBatis將執行SQL語句,並將可能返回的結果集轉換成List<E> 列表。MyBatis在對結果集的處理中,支援結果集關係一對多和多對一的轉換,並且有兩種支援方式,一種為巢狀查詢語句的查詢,還有一種是巢狀結果集的查詢。
3. 框架支撐層
3.1. 事務管理機制
事務管理機制對於ORM框架而言是不可缺少的一部分,事務管理機制的質量也是考量一個ORM框架是否優秀的一個標準,對於資料管理機制我已經在我的博文 中有非常詳細的討論,感興趣的讀者可以點選檢視。
3.2. 連線池管理機制
由於建立一個數據庫連線所佔用的資源比較大, 對於資料吞吐量大和訪問量非常大的應用而言,連線池的設計就顯得非常重要,對於連線池管理機制我已經在我的博文 中有非常詳細的討論,感興趣的讀者可以點選檢視。
3.3. 快取機制
為了提高資料利用率和減小伺服器和資料庫的壓力,MyBatis會對於一些查詢提供會話級別的資料快取,會將對某一次查詢,放置到SqlSession中,在允許的時間間隔內,對於完全相同的查詢,MyBatis會直接將快取結果返回給使用者,而不用再到資料庫中查詢。(至於具體的MyBatis快取機制,我將準備專門一篇文章來討論,敬請關注~)
3. 4. SQL語句的配置方式
傳統的MyBatis配置SQL語句方式就是使用XML檔案進行配置的,但是這種方式不能很好地支援面向介面程式設計的理念,為了支援面向介面的程式設計,MyBatis引入了Mapper介面的概念,面向介面的引入,對使用註解來配置SQL語句成為可能,使用者只需要在介面上新增必要的註解即可,不用再去配置XML檔案了,但是,目前的MyBatis只是對註解配置SQL語句提供了有限的支援,某些高階功能還是要依賴XML配置檔案配置SQL語句。
4 引導層
引導層是配置和啟動MyBatis配置資訊的方式。MyBatis提供兩種方式來引導MyBatis:基於XML配置檔案的方式和基於Java API的方式,讀者可以參考我的另一片博文:
二、MyBatis的主要構件及其相互關係
從MyBatis程式碼實現的角度來看,MyBatis的主要的核心部件有以下幾個:
- SqlSession 作為MyBatis工作的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
- Executor MyBatis執行器,是MyBatis 排程的核心,負責SQL語句的生成和查詢快取的維護
- StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合。
- ParameterHandler 負責對使用者傳遞的引數轉換成JDBC Statement 所需要的引數,
- ResultSetHandler 負責將JDBC返回的ResultSet結果集物件轉換成List型別的集合;
- TypeHandler 負責java資料型別和jdbc資料型別之間的對映和轉換
- MappedStatement MappedStatement維護了一條<select|update|delete|insert>節點的封裝,
- SqlSource 負責根據使用者傳遞的parameterObject,動態地生成SQL語句,將資訊封裝到BoundSql物件中,並返回
- BoundSql 表示動態生成的SQL語句以及相應的引數資訊
- Configuration MyBatis所有的配置資訊都維持在Configuration物件之中。
(注:這裡只是列出了我個人認為屬於核心的部件,請讀者不要先入為主,認為MyBatis就只有這些部件哦!每個人對MyBatis的理解不同,分析出的結果自然會有所不同,歡迎讀者提出質疑和不同的意見,我們共同探討~)
它們的關係如下圖所示:
三、從MyBatis一次select 查詢語句來分析MyBatis的架構設計
一、資料準備(非常熟悉和應用過MyBatis 的讀者可以迅速瀏覽此節即可)
1. 準備資料庫資料,建立EMPLOYEES表,插入資料:
[sql] view plain copy print?
- --建立一個員工基本資訊表
- createtable"EMPLOYEES"(
- "EMPLOYEE_ID" NUMBER(6) notnull,
- "FIRST_NAME" VARCHAR2(20),
- "LAST_NAME" VARCHAR2(25) notnull,
- "EMAIL" VARCHAR2(25) notnullunique,
- "SALARY" NUMBER(8,2),
- constraint"EMP_EMP_ID_PK"primarykey ("EMPLOYEE_ID")
- );
- comment ontable EMPLOYEES is'員工資訊表';
- comment oncolumn EMPLOYEES.EMPLOYEE_ID is'員工id';
- comment oncolumn EMPLOYEES.FIRST_NAME is'first name';
- comment oncolumn EMPLOYEES.LAST_NAME is'last name';
- comment oncolumn EMPLOYEES.EMAIL is'email address';
- comment oncolumn EMPLOYEES.SALARY is'salary';
- --新增資料
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (100, 'Steven', 'King', 'SKING', 24000.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
- insertinto EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
- values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
--建立一個員工基本資訊表 create table "EMPLOYEES"( "EMPLOYEE_ID" NUMBER(6) not null, "FIRST_NAME" VARCHAR2(20), "LAST_NAME" VARCHAR2(25) not null, "EMAIL" VARCHAR2(25) not null unique, "SALARY" NUMBER(8,2), constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID") ); comment on table EMPLOYEES is '員工資訊表'; comment on column EMPLOYEES.EMPLOYEE_ID is '員工id'; comment on column EMPLOYEES.FIRST_NAME is 'first name'; comment on column EMPLOYEES.LAST_NAME is 'last name'; comment on column EMPLOYEES.EMAIL is 'email address'; comment on column EMPLOYEES.SALARY is 'salary'; --新增資料 insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (100, 'Steven', 'King', 'SKING', 24000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (105, 'David', 'Austin', 'DAUSTIN', 4800.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00); insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY) values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);
2. 配置Mybatis的配置檔案,命名為mybatisConfig.xml:
[html] view plain copy print?
- <?xmlversion="1.0"encoding="utf-8"?>
- <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
- <environmentsdefault="development">
- <environmentid="development">
- <transactionManagertype="JDBC"/>
- <dataSourcetype="POOLED">
- <propertyname="driver"value="oracle.jdbc.driver.OracleDriver"/>
- <propertyname="url"value="jdbc:oracle:thin:@localhost:1521:xe"/>
- <propertyname="username"value="louis"/>
- <propertyname="password"value="123456"/>
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapperresource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
- </mappers>
- </configuration>
<?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> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" /> <property name="username" value="louis" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> <mappers> <mapper resource="com/louis/mybatis/domain/EmployeesMapper.xml"/> </mappers> </configuration>
3. 建立Employee實體Bean 以及配置Mapper配置檔案
[java] view plain copy print?
- package com.louis.mybatis.model;
- import java.math.BigDecimal;
- publicclass Employee {
- private Integer employeeId;
- private String firstName;
- private String lastName;
- private String email;
- private BigDecimal salary;
- public Integer getEmployeeId() {
- return employeeId;
- }
- publicvoid setEmployeeId(Integer employeeId) {
- this.employeeId = employeeId;
- }
- public String getFirstName() {
- return firstName;
- }
- publicvoid setFirstName(String firstName) {
- this.firstName = firstName;
- }
- public String getLastName() {
- return lastName;
- }
- publicvoid setLastName(String lastName) {
- this.lastName = lastName;
- }
- public String getEmail() {
- return email;
- }
- publicvoid setEmail(String email) {
- this.email = email;
- }
- public BigDecimal getSalary() {
- return salary;
- }
- publicvoid setSalary(BigDecimal salary) {
- this.salary = salary;
- }
- }
package com.louis.mybatis.model; import java.math.BigDecimal; public class Employee { private Integer employeeId; private String firstName; private String lastName; private String email; private BigDecimal salary; public Integer getEmployeeId() { return employeeId; } public void setEmployeeId(Integer employeeId) { this.employeeId = employeeId; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public BigDecimal getSalary() { return salary; } public void setSalary(BigDecimal salary) { this.salary = salary; } }
[html] view plain copy print?
- <?xmlversion="1.0"encoding="UTF-8"?>
- <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
- <mappernamespace="com.louis.mybatis.dao.EmployeesMapper">
- <resultMapid="BaseResultMap"type="com.louis.mybatis.model.Employee">
- <idcolumn="EMPLOYEE_ID"property="employeeId"jdbcType="DECIMAL"/>
- <resultcolumn="FIRST_NAME"property="firstName"jdbcType="VARCHAR"/>
- <resultcolumn="LAST_NAME"property="lastName"jdbcType="VARCHAR"/>
- <resultcolumn="EMAIL"property="email"jdbcType="VARCHAR"/>
- <resultcolumn="SALARY"property="salary"jdbcType="DECIMAL"/>
- </resultMap>
- <selectid="selectByPrimaryKey"resultMap="BaseResultMap"parameterType="java.lang.Integer">
- select
- EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
- from LOUIS.EMPLOYEES
- where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
- </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="com.louis.mybatis.dao.EmployeesMapper" > <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" > <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" /> <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" /> <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" /> <result column="EMAIL" property="email" jdbcType="VARCHAR" /> <result column="SALARY" property="salary" jdbcType="DECIMAL" /> </resultMap> <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" > select EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY from LOUIS.EMPLOYEES where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL} </select> </mapper>
4. 建立eclipse 或者myeclipse 的maven專案,maven配置如下:
[html] view plain copy print?
- <projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <modelVersion>4.0.0</modelVersion>
- <groupId>batis</groupId>
- <artifactId>batis</artifactId>
- <version>0.0.1-SNAPSHOT</version>
- <packaging>jar</packaging>
- <name>batis</name>
- <url>http://maven.apache.org</url>
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- </properties>
- <dependencies>
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>3.8.1</version>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.mybatis</groupId>
- <artifactId>mybatis</artifactId>
- <<