Mybatis詳解系列(一)--持久層框架解決了什麼及如何使用Mybatis
阿新 • • 發佈:2020-03-31
# 簡介
Mybatis 是一個持久層框架,它對 JDBC 進行了高階封裝,使我們的程式碼中不會出現任何的 JDBC 程式碼,另外,它還通過 xml 或註解的方式將 sql 從 DAO/Repository 層中解耦出來,除了這些基本功能外,它還提供了動態 sql、延遲載入、快取等功能。 相比 Hibernate,Mybatis 更面向資料庫,可以靈活地對 sql 語句進行優化。
針對 Mybatis 的分析,我會拆分成**使用、配置、原始碼、生成器**等部分,都放在 [Mybatis]( https://www.cnblogs.com/ZhangZiSheng001/category/1685176.html ) 這個系列裡,內容將持續更新。本文是這個系列的第一篇文章,將從以下兩個問題展開 :
1. 持久層框架解決了哪些問題?
2. 如何使用 Mybatis(這裡會從入門到深入)?
# 專案環境的說明
為了更好地分析 Mybatis 的特性,本專案不會引入任何的依賴注入框架,將使用比較原生態的方式來使用 Mybatis。
## 工程環境
JDK:1.8.0_231
maven:3.6.1
IDE:Spring Tool Suites4 for Eclipse 4.12 (裝有 Mybatipse 外掛)
mysql:5.7.28
## 依賴引入
Mybatis 有自帶的連線池,但實際專案中建議還是引入第三方的比較好。
```xml
org.Mybatis
Mybatis
3.5.4
mysql
mysql-connector-java
8.0.15
ch.qos.logback
logback-core
1.2.3
jar
ch.qos.logback
logback-classic
1.2.3
jar
com.zaxxer
HikariCP
2.6.1
junit
junit
4.12
test
```
## 資料庫指令碼
在這個專案裡面,我希望儘可能地模擬出實際專案的各種場景,例如,高階條件查詢、關聯查詢(一對一關聯、多對多關聯和自關聯),並研究在對應場景下如何使用 Mybatis 解決問題。本專案的 ER 圖如下,涉及到 4 張主表和 2 張中間表,具體的[sql指令碼](https://github.com/ZhangZiSheng001/mybatis-projects/sql)也提供好了:
![Mybatis_demo專案的ER圖](https://img2020.cnblogs.com/blog/1731892/202003/1731892-20200331111457093-813152020.png)
# 持久層框架解決了哪些問題
在分析如何使用 mybatis 之前,我們先來研究一個問題:持久層框架解決了哪些問題?
假設沒有持久層框架,首先想到的就是使用 JDBC 來操作資料庫。這裡我簡單地引入一個需求,就是我想通過 id 查詢出一個員工物件。下面僅會從repository/DAO 層的角度來考慮如何實現,所以我們不需要去考慮 service 層中**事務提交**和**連線關閉**的問題,當然,這樣會遇到一個問題,就是我們必須保證 service 層的事務和持久層的是同一個,這一點會通過 **Utils 來解決,因為不是本文重點,這裡不展開。
## 用 JDBC 方式查詢一個員工
下面用 JDBC 查詢員工物件。
有人可能會問,就算你不適用持久層框架,你還可以使用 DBUtils 或者自己封裝 JDBC 程式碼啊?這裡需要強調下,這種封裝其實是持久層框架應該做的事,我們自己手動封裝,其實已經在實現一個持久層框架了。所以,為了暴露純粹的 JDBC 實現的缺點,這裡儘量不去封裝。
```java
@Override
public Employee selectByPrimaryKey(String id) throws SQLException {
Employee employee = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
// 建立sql
String sql = "select * from demo_employee where id = ?";
try {
// 獲得連線(JDBCUtils保證同一執行緒獲得同一個連線物件)
Connection connection = JDBCUtils.getConnection();
// 獲得Statement物件
statement = connection.prepareStatement(sql);
// 設定引數
statement.setObject(1, id);
// 執行,獲取結果集
resultSet = statement.executeQuery();
if(resultSet.next()) {
// 對映結果集
employee = convert(resultSet);
}
// 返回員工物件
return employee;
} finally {
// 釋放資源
JDBCUtils.release(null, statement, resultSet);
}
}
/**
*
* @author: zzs
* @date: 2020年3月28日 下午12:20:02
* @param resultSet
* @return: Employee
* @throws SQLException
*/
private Employee convert(ResultSet resultSet) throws SQLException {
Employee employee = new Employee();
employee.setId(resultSet.getString("id"));
employee.setName(resultSet.getString("name"));
employee.setGender(resultSet.getBoolean("gender"));
employee.setNo(resultSet.getString("no"));
employee.setAddress(resultSet.getString("address"));
employee.setDeleted(resultSet.getBoolean("deleted"));
employee.setDepartmentId(resultSet.getString("department_id"));
employee.setPassword(resultSet.getString("password"));
employee.setPhone(resultSet.getString("phone"));
employee.setStatus(resultSet.getByte("status"));
employee.setCreate(resultSet.getDate("gmt_create"));
employee.setModified(resultSet.getDate("gmt_modified"));
return employee;
}
```
通過上面的程式碼,我們可以看到兩個主要的問題:
1. **每個 Repository/DAO 方法都會出現繁瑣、重複的 JDBC 程式碼**。
2. **sql 和 DAO/Repository 的程式程式碼耦合度太高,不能統一管理**。這裡的 sql 包括了 sql 的定義、引數設定和結果集對映,強調一點,**不是說 sql 不能出現在 java 類中,而是說應該從 DAO/Repository 的程式程式碼中解耦出來,進行集中管理**。
說到這裡,我們可以總結出來,為了專案的方便和解耦,一個基本的持久層框架需要做到:
1. **對 JDBC 程式碼進行高階封裝,為我們提供更簡單的介面**。
2. **將 sql 從 DAO/Repository 中解耦出來**。
Mybatis 作為一個優秀的持久層框架,針對以上問題提供瞭解決方案,下面我們再看看使用 Mybatis 如何實現上面的需求。
## 用 Mybatis 方式查詢一個員工
還是通過查詢員工的例子來說明,程式碼如下:
```java
@Override
public Employee selectByPrimaryKey(String id) {
// 獲取sqlSession
SqlSession sqlSession = MybatisUtils.getSqlSession();
// 獲取Mapper
EmployeeMapper baseMapper = sqlSession.getMapper(EmployeeMapper.class);
// 執行,獲取員工物件
Employee employee = baseMapper.selectByPrimaryKey(id);
// 返回物件
return employee;
}
```
上面的程式碼沒有出現任何的 JDBC 程式碼和 sql 程式碼,因為 Mybatis 對 JDBC 進行了高階封裝,並且採用 Mapper 的註解或 xml 檔案來統一管理 sql 的定義、引數設定和結果集對映。下面看下 xml 檔案的方式:
```xml
e.id,
e.`name`,
e.gender,
e.no,
e.password,
e.phone,
e.address,
e.status,
e.deleted,
e.department_id,
e.gmt_create,
e.gmt_modified
```
針對 sql 解耦的問題,早期的持久層框架都偏向於將 sql 獨立在配置檔案中,後來才逐漸引入註解的支援,如下是Mybatis 的註解方式(EmployeeMapper 介面):
```java
@Select("SELECT e.id, e.`name`, e.gender, e.no, e.password, e.phone, e.address, e.status, e.deleted, e.department_id, e.gmt_create, e.gmt_modified FROM demo_employee e WHERE id = #{id}")
@resultMap("BaseResultMap")
Employee selectByPrimaryKey(String id);
```
我認為,正如前面說到的,sql 在專案中存在形式不是重點,我們的目的是希望 sql 能被統一管理,基於這個目的實現的不同方案,都是合理的。
Mybatis 作為一款優秀的持久層框架,除了解決上面的兩個基本問題,還為我們提供了懶載入、快取、動態語句、外掛等功能,下文會講到。
## 補充
通過上面的內容,我們已經回答了問題:持久層框架解決了哪些問題?這裡需要補充一點:
本文只是指出持久層框架的需要解決的基本問題,並沒有強調必須使用 Mybatis 或 Hibernate 等通用框架。出於效能方面的考慮,部分開發者可能會採用更輕量的實現,而不是使用流行的通用框架。當然,這也是自己造輪子和使用通用輪子的區別了。
# 如何使用 Mybatis
本專案會模擬實際開發的各種場景來研究 Mybatis 的使用方法。在我看來,在專案建立時,repository/DAO 層只要有以下幾個方法,已經可以滿足大部分使用需求。**在 repository/DAO 層定義大量的`*By*`方法是非常低階和不負責任的**,然而,我接觸過許多人都是這麼搞的。
```java
public interface IEmployeeRepository {
// 查詢
Employee get(String id);//根據id查詢
List list(EmployeeCondition con);//根據條件查詢
long count(EmployeeCondition con);//根據條件查詢數量
// 刪除
int delete(EmployeeCondition con);//根據條件刪除
int delete(String id);//根據id刪除
// 新增
int save(Employee employee);//新增
int save(List list);//批量新增
// 更新
int update(Employee employee, EmployeeCondition con);//根據條件更新
int update(Employee employee);//更新
}
```
下面的使用例子將針對這個介面進行展開,主要分成4個部分:
1. 入門例子。通過 根據id查詢員工和新增員工 的例子說明;
2. 高階條件查詢。
3. 關聯查詢。這裡會查詢員工並帶出部門、角色,並且結合懶載入使用。
## 從入門例子開始
本文的包結構如下。test 裡的測試簡單看成是 service 層在呼叫 respository 層的方法,由於我必須在 service 層 和 respository 層中拿到同一個“連線”來管理事務或釋放資源,所有 util 中將“連線”繫結到了當前執行緒。
在進行下面工作之前,我們需要先建立好實體和 mapper 檔案(如圖圈紅部分),實際專案中,我們可以使用 [Mybatis-generator]() 或者自定義的程式碼生成器生成,mapper 中將包含基本的 CRUD 程式碼。
### 配置 configuration 檔案
這個是 Mybatis 的主配置檔案,它**影響著 Mybatis 的行為和屬性資訊**。配置檔案的層級結構如下:
configuration(配置)
- properties(屬性)
- settings(設定)
- typeAliases(類型別名)
- typeHandlers(型別處理器)
- objectFactory(物件工廠)
- plugins(外掛)
- environments(環境配置)
- environment(環境變數)
- transactionManager(事務管理器)
- dataSource(資料來源)
- databaseIdProvider(資料庫廠商標識)
- mappers(對映器)
作為入門例子,這裡只進行了簡單的配置,本系列的第二篇部落格將詳細講解這些配置。
注意:**configuration 的標籤必須按順序寫,不然會報錯。**
```xml
```
以上配置作用如下:
1. **typeAliases**:類型別名,僅在 *Mapper.xml 中使用。通過配置實體類的包名,我們可以在 xml 中直接通過 Employee 來表示員工型別,而不需要使用全限定類名;
2. **environments**:環境配置。下篇文章再詳細講吧。這裡簡單說下 dataSource,由於引入的是第三方資料來源,所以得重寫 org.apache.ibatis.datasource.DataSourceFactory介面,如下:
```java
public class HikariDataSourceFactory implements DataSourceFactory {
private DataSource dataSource;
public HikariDataSourceFactory() {
super();
try {
HikariConfig config = new HikariConfig("/hikari.properties");
dataSource = new HikariDataSource(config);
} catch(Exception e) {
throw new RuntimeException("建立資料來源失敗", e);
}
}
@Override
public DataSource getDataSource() {
return dataSource;
}
@Override
public void setProperties(Properties props) {
// TODO Auto-generated method stub
}
}
```
3. **mappers**:對映器。其實就是告訴 Mybatis 對映器放在哪裡,注意,Mapper 介面和 xml 檔案**編譯打包後**必須在同一個路徑下。如果你的 xml 檔案放在 src/main/java 中(不建議),需要在 pom 檔案中增加以下配置:
```xml
src/main/java
false
**/mapper/*.xml
src/main/resources
**
```
### 配置 mapper xml檔案
Mybatis 的對映檔案只有很少的幾個頂級元素(按照應被定義的順序列出):
- `cache` – 該名稱空間的快取配置。
- `cache-ref` – 引用其它名稱空間的快取配置。
- `resultMap` – 描述如何從資料庫結果集中載入物件,是最複雜也是最強大的元素。
- `parameterMap` – 老式風格的引數對映。此元素已被廢棄,並可能在將來被移除!請使用行內參數對映。文件中不會介紹此元素。
- `sql` – 可被其它語句引用的可重用語句塊。
- `insert` – 對映插入語句。
- `update` – 對映更新語句。
- `delete` – 對映刪除語句。
- `select` – 對映查詢語句。
這裡也只進行了簡單的配置,本系列的第二篇部落格再詳細講。
```xml
e.id,
e.`name`,
e.gender,
e.no,
e.password,
e.phone,
e.address,
e.status,
e.deleted,
e.department_id,
e.gmt_create,
e.gmt_modified
insert into
demo_employee
(id, name, gender,no, password, phone,address, status, deleted,department_id, gmt_create, gmt_modified)
values (
#{id,jdbcType=VARCHAR},
#{name,jdbcType=VARCHAR},
#{gender,jdbcType=BIT},
#{no,jdbcType=VARCHAR},
#{password,jdbcType=VARCHAR},
#{phone,jdbcType=VARCHAR},
#{address,jdbcType=VARCHAR},
#{status,jdbcType=TINYINT},
#{deleted,jdbcType=BIT},
#{departmentId,jdbcType=VARCHAR},
#{create,jdbcType=TIMESTAMP},
#{modified,jdbcType=TIMESTAMP}
)
```
在以上配置中,使用了三個元素:
1. **resultMap**:表列名(或查詢出來的別名)與實體屬性的對映關係。除了 id 和關聯物件欄位外,只要表列名(或查詢出來的別名)與實體屬性一致,可以不用配置。
2. **sql**: 用來定義可重用的 SQL 程式碼片段,可以在查詢或變更語句中通過 include 引用。如果資料庫的欄位名和實體類的不一致,需要設定列別名。
3. **select**: 查詢語句。其中,id 是所在名稱空間中唯一的識別符號,可以被用來引用這條語句,與 mapper 檔案中的,parameterType 是入參型別,resultMap 是對映表。
4. **insert**:插入語句。
注意下引數符號 #{id }, 它告訴 Mybatis 建立一個預處理語句(PreparedStatement)引數,在 JDBC 中,這樣的一個引數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中。不過有時你就是想直接在 SQL 語句中直接插入一個不轉義的字串。 比如 ORDER BY 子句,這時候你可以使用 “$” 字元:
```xml
ORDER BY ${columnName}
```
### 獲取 SqlSession
在以下程式碼中,存在三個主要物件:
1. `SqlSessionFactoryBuilder` :一旦建立了 SqlSessionFactory,就不再需要它了,因此 SqlSessionFactoryBuilder 例項的最佳作用域是方法作用域。
2. `SqlSessionFactory`:一旦被建立就應該在應用的執行期間一直存在,沒有任何理由丟棄它或重新建立另一個例項, 因此 SqlSessionFactory 的最佳作用域是應用作用域。
3. `SqlSession`:每個執行緒都應該有它自己的 SqlSession 例項。SqlSession 的例項不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。 **SqlSession 的作用類似於 JDBC 的`Connection`**,使用完後必須 close。
```java
// 載入配置檔案,初始化SqlSessionFactory物件
String resource = "Mybatis-config.xml";
InputStream in = Resources.getResourceAsStream(resource));
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
// 獲取sqlSession 注意,這種方式獲取的 SqlSession 需手動提交事務。
SqlSession sqlSession = sqlSessionFactory.openSession();
```
為了保證同一個執行緒在 service 和 repository 中拿到同一個 SqlSession 物件,本專案中定義了工具類 cn.zzs.Mybatis.util.MybatisUtils 來獲取 SqlSession。
```java
public class MybatisUtils {
private static SqlSessionFactory sqlSessionFactory;
private static ThreadLocal tl = new ThreadLocal<>();
private static final Object obj = new Object();
static {
init();
}
/**
*
*
insert into
demo_employee
(id, name, gender,no, password, phone,address, status, deleted,department_id, gmt_create, gmt_modified)
values
(
#{item.id},
#{item.name},
#{item.gender},
#{item.no},
#{item.password},
#{item.phone},
#{item.address},
#{item.status},
#{item.deleted},
#{item.departmentId},
#{item.create},
#{item.modified}
)
```
## 高階條件查詢
還是回到下面這個介面,經過上面的例子,圈紅的幾個方法,相信大家已經知道如何使用。現在看看高階條件查詢。
### 條件類和它的繼承體系
在專案中,條件類經常會被用來接收各種查詢條件,當業務比較複雜時,條件類會非常臃腫,大部分原因都是寫程式碼不遵循規範。我們的條件封裝類的條件由三個部分組成(以員工條件類為例):
1. 不同實體都會用到的條件,例如頁碼頁數;
2. 對應實體的屬性,例如員工性別、電話號碼;
3. 與對應實體關聯的實體屬性,例如員工所在部門名。
根據這種結構可以形成以下的繼承結構:
其中,`BaseCondition`中用於定義一些不同實體都通用的條件,如下:
```java
public class BaseCondition {
/**
* 頁碼
*/
private Integer pageNum;
/**
* 每頁記錄數
*/
private Integer pageSize;
/**
* 排序語句
*/
private String orderByClause;
/**
* 關鍵字
*/
private String searchKeyWord;
/**
* 是否去重
*/
private boolean distinct;
// 省略setter/getter方法
}
```
AbstractEmployeeCondition 中定義屬於員工類的條件,如下:
```java
public abstract class AbstractEmployeeCondition extends BaseCondition {
/**
* 注意,這裡不要命名為id
*/
private String employeeId;
private String name;
private Boolean gender;
private String no;
private String password;
private String phone;
private String address;
private Byte status;
private Boolean deleted;
private String departmentId;
private Date createStart;
private Date createEnd;
private Date modifiedStart;
private Date modifiedEnd;
// 省略setter/getter方法
}
```
接下來是具體實現類 EmployeeCondition,這裡用於定義一些關聯實體的條件,也就是說使用到這些條件時必須 join 表。
```java
public class EmployeeCondition extends AbstractEmployeeCondition {
//============部門表============
/**
*
and
e.name = #{con.name}
and
e.gender = #{con.gender}
and
e.no = #{con.no}
and
e.password = #{con.password}
and
e.phone = #{con.phone}
and
e.address = #{con.address}
and
e.status = #{con.status}
and
e.deleted = #{con.deleted}
and
e.gmt_create > #{con.createStart}
and
e.gmt_create < #{con.createEnd}
and
e.gmt_modified > #{con.modifiedStart}
and
e.gmt_modified < #{con.modifiedEnd}
and
d.no = #{con.departmentNo}
and
d.name = #{con.departmentName}
inner join
demo_department d
```
這裡的 sql 將條件分離出來複用,並沿用了條件實體的繼承關係,有利於後續專案維護和擴充套件。注意,千萬不要等到專案很臃腫時再進行 sql 的抽取複用。
上面高階條件查詢的程式碼,實際專案中可以通過程式碼生成器生成,擴充套件的條件再手動新增就行了。
### 編寫測試方法
其實,這裡存在一個問題,排序條件那裡 sql 語句滲透到了 service 層,實際專案中,排序規則不會經常變動,我們可以在 xml 裡直接使用預設排序條件,條件類增加 userDefaultSort 屬性來判斷。總之要記住一點,在 service 層中滲透 sql 程式碼,是非常不應該的!
```java
@Test
public void testList() {
EmployeeCondition con = new EmployeeCondition();
// 設定條件
con.setGender(false);
con.setAddress("北京");
con.setDeleted(false);
con.setPhone("18826****41");
con.setDistinct(true);
con.setDepartmentNo("202003230002");
// 設定排序規則
con.setOrderByClause("name desc");// 注意為資料庫欄位
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
// 執行
List list = employeeRepository.list(con);
// 遍歷結果
list.forEach(System.out::println);
}
}
```
### 測試
測試以上方法,在控制檯列印以下sql,我格式化了下,可以打看到 mybatis 幫我們拼接好了查詢條件。
```sql
SELECT DISTINCT e.id, e.`name`, e.gender, e.no, e.password
, e.phone, e.address, e.status, e.deleted, e.department_id
, e.gmt_create, e.gmt_modified
FROM demo_employee e
INNER JOIN demo_department d
WHERE 1 = 1
AND e.gender = ?
AND e.phone = ?
AND e.address = ?
AND e.deleted = ?
AND d.no = ?
ORDER BY name DESC
```
## 關聯查詢
以上基本講完 IEmployeeRepository 中的方法,我前面說過,IEmployeeRepository 介面中的方法可以滿足大部分的使用需求,但是,如果我響應給前端的資料中,除了員工的欄位,還需要員工所在部門和員工擁有的角色的欄位,這時 IEmployeeRepository 的方法不就應付不了了嗎?
這種場景涉及到的就是關聯查詢,我需要在 repository 層查詢員工物件時將部門和角色一併查出來,然後在前端轉換為具體的 VO 物件。
### 修改實體類
在員工的實體增加以下兩個屬性,專案中也可以建立一個 Employee 的子類,將關聯的屬性放入到子類裡,以便於管理。
```java
public class Employee {
private Department department;
private List roles = Collections.emptyList();
//······
}
```
### 修改 mapper xml檔案
關聯查詢時,我們不需要修改原來的查詢語句,只需要修改 resultMap 就行。
```java
```
以上增加了兩個標籤,association 和 collection 分別用於配置一方和多方的關聯,其中 property 對應實體中的屬性,column 對應執行語句返回的欄位(如果沒有使用別名的話,一般為列名),select 指向了其他 mapper 語句的 id。
### 編寫測試方法
我呼叫的還是 IEmployeeRepository 介面的 get 方法,只是增加了部門和角色的列印。
```java
@Test
public void testGetRelation() {
String id = "cc6b08506cdb11ea802000fffc35d9fe";
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
// 執行,獲取員工物件
Employee employee = employeeRepository.get(id);
// 列印員工
System.out.println(employee);
// 列印部門
System.out.println(employee.getDepartment());
// 列印角色
employee.getRoles().forEach(System.out::println);
}
}
```
### 測試
測試上面的方法,可以看到控制檯列印了三個查詢語句,分別對應員工、部門和角色,這樣,我們就可以在前端轉換 VO 時,將部門和角色的欄位都給到 VO 了。
### 補充--延遲載入
上面的關聯查詢存在一個問題:如果我前端的 VO 只想要部門的欄位而不需要角色,或者我就只要員工的欄位,但是,這個時候還是會把部門和角色統統查出來,將會產生不必要的效能損耗。
這種情況,就需要使用延遲載入了。延遲載入可以保證關聯物件只有在用到的時候才去執行資料庫查詢。
#### 配置延遲載入
Mybatis 延遲載入功能預設是不開啟的,但配置開啟也很簡單,只要在主配置檔案中增加:
```xml
```
#### 編寫測試方法
還是使用前面的方法,這裡我把 role 部分的程式碼註釋掉。
```java
@Test
public void testGetLazy() {
String id = "cc6b08506cdb11ea802000fffc35d9fe";
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
// 執行,獲取員工物件
Employee employee = employeeRepository.get(id);
// 列印員工
System.out.println(employee);
// 列印部門
System.out.println(employee.getDepartment());
// 列印角色
// employee.getRoles().forEach(System.out::println);
}
}
```
#### 測試
測試上面的方法,可以看到只關聯查出了部門,角色並沒有查出來,因為我們程式程式碼中沒有觸發 getRoles() 或 lazyLoadTriggerMethods 的操作。實際專案中,我們在前端轉換 VO 時,如果需要用到關聯物件的欄位,才會觸發查詢。
## 分頁查詢
最後再簡單查下分頁查詢,這裡使用 pagehelper 外掛來實現。
### 引入外掛依賴
在專案 pom.xml 檔案中增加以下依賴。
```xml
com.github.pagehelper
pagehelper
5.1.10
```
### 修改 mybatis 主配置檔案
在 mybatis 主配置檔案中增加 plugins 元素,並引入分頁外掛。
```xml
```
### 編寫測試方法
```java
@Test
public void testlistPage() {
EmployeeCondition con = new EmployeeCondition();
// 設定條件
con.setGender(false);
con.setAddress("北京");
con.setDeleted(false);
con.setPhone("18826****41");
con.setDistinct(true);
try (SqlSession sqlSession = MybatisUtils.getSqlSession()) {
// 設定分頁資訊
PageHelper.startPage(0, 3);
// 執行查詢
List list = employeeRepository.list(con);
// 封裝分頁模型
PageInfo pageInfo = new PageInfo<>(list);
// 取分頁模型的資料
System.out.println("查詢總數" + pageInfo.getTotal());
}
}
```
### 測試
測試上面的方法,可以看到先進行了總數的查詢,再進行分頁查詢。
```sql
2020-03-31 11:06:59.618 c.z.m.m.EmployeeMapper.selectByCondition_COUNT -
==> Preparing:
SELECT COUNT(0)
FROM (
SELECT DISTINCT e.id, e.`name`, e.gender, e.no, e.password
, e.phone, e.address, e.status, e.deleted, e.department_id
, e.gmt_create, e.gmt_modified
FROM demo_employee e
WHERE 1 = 1
AND e.gender = ?
AND e.phone = ?
AND e.address = ?
AND e.deleted = ?
) table_count
2020-03-31 11:06:59.646 c.z.m.m.EmployeeMapper.selectByCondition_COUNT -
==> Parameters: false(Boolean), 18826****41(String), 北京(String), false(Boolean)
2020-03-31 11:06:59.678 c.z.m.m.EmployeeMapper.selectByCondition_COUNT -
<== Total: 1
2020-03-31 11:06:59.693 c.z.m.mapper.EmployeeMapper.selectByCondition -
==> Preparing:
SELECT DISTINCT e.id, e.`name`, e.gender, e.no, e.password
, e.phone, e.address, e.status, e.deleted, e.department_id
, e.gmt_create, e.gmt_modified
FROM demo_employee e
WHERE 1 = 1
AND e.gender = ?
AND e.phone = ?
AND e.address = ?
AND e.deleted = ?
LIMIT ?
2020-03-31 11:06:59.693 c.z.m.mapper.EmployeeMapper.selectByCondition -
==> Parameters: false(Boolean), 18826****41(String), 北京(String), false(Boolean), 3(Integer)
2020-03-31 11:06:59.724 c.z.m.mapper.EmployeeMapper.selectByCondition -
<== Total: 3
查詢總數4
```
# 參考資料
[Mybatis官方中文文件](https://Mybatis.org/Mybatis-3/zh/index.html/)
> 相關原始碼請移步:[mybatis-demo](https://github.com/ZhangZiSheng001/mybatis-projects/tree/master/mybatis-demo)
> 本文為原創文章,轉載請附上原文出處連結:[https://www.cnblogs.com/ZhangZiSheng001/p/12603885.html](https://www.cnblogs.com/ZhangZiSheng001/p/126038
通過結果集構造員工物件
獲取SqlSession物件的方法,執行緒安全
* @author: zzs * @date: 2019年8月31日 下午9:22:29 * @return: SqlSession */ public static SqlSession getSqlSession() { // 從當前執行緒中獲取連線物件 SqlSession sqlSession = tl.get(); // 判斷為空的話,建立連線並繫結到當前執行緒 if(sqlSession == null) { synchronized(obj) { if((sqlSession = tl.get()) == null) { sqlSession = sqlSessionFactory.openSession(); tl.set(sqlSession); } } } return sqlSession; } /** *根據指定配置檔案初始化SqlSessionFactory物件
* @author: zzs * @date: 2019年9月1日 上午10:53:05 * @return: void */ private static void init() { try (InputStream inputStream = Resources.getResourceAsStream("Mybatis-config.xml")) { // 載入配置檔案,初始化SqlSessionFactory物件 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch(IOException e) { throw new RuntimeException("建立sqlSessionFactory失敗", e); } } } ``` ### 編寫 Repository repository 的程式碼非常簡單,只需要拿到 SqlSessionn 物件,就能直接進行資料庫操作了。注意,**這裡的 SqlSession 不能作為例項變數**。 ```java public class EmployeeRepository implements IEmployeeRepository { @Override public Employee get(String id) { return MybatisUtils.getSqlSession().getMapper(EmployeeMapper.class).selectByPrimaryKey(id); } @Override public int save(Employee employee) { return MybatisUtils.getSqlSession().getMapper(EmployeeMapper.class).insert(employee); } } ``` ### 編寫測試類 測試類簡單看成是一個 service 類(當然,它不完全是),這裡需要手動地提交事務和釋放資源。 ```java public class EmployeeRepositoryTest { private IEmployeeRepository employeeRepository = new EmployeeRepository(); @Test public void testGet() { String id = "cc6b08506cdb11ea802000fffc35d9fe"; try (SqlSession sqlSession = MybatisUtils.getSqlSession();) { // 執行,獲取員工物件 Employee employee = employeeRepository.get(id); // 列印 System.out.println(employee); } } } @Test public void testSave() { // 建立使用者 Employee employee = new Employee(UUID.randomUUID().toString().replace("-", ""), "zzs005", true, "zzs005", "admin", "18826****41", "廣東", (byte)1, false, "94e2d2e56cd811ea802000fffc35d9fa", new Date(), new Date()); try (SqlSession sqlSession = MybatisUtils.getSqlSession()) { // 儲存 employeeRepository.save(employee); // 提交事務 sqlSession.commit(); } } ``` ### 測試 測試上面兩個方法,會在控制檯輸出了 sql。為了直觀點,我這裡格式化了一下。 ```sql 2020-03-30 20:40:11.098 c.z.m.mapper.EmployeeMapper.selectByPrimaryKey - ==> Preparing: SELECT e.id, e.`name`, e.gender, e.no, e.password , e.phone, e.address, e.status, e.deleted, e.department_id , e.gmt_create, e.gmt_modified FROM demo_employee e WHERE e.id = ? 2020-03-30 20:40:11.121 c.z.m.mapper.EmployeeMapper.selectByPrimaryKey - ==> Parameters: cc6b08506cdb11ea802000fffc35d9fe(String) 2020-03-30 20:40:11.170 c.z.m.mapper.EmployeeMapper.selectByPrimaryKey - <== Total: 1 Employee [id=cc6b08506cdb11ea802000fffc35d9fe, name=zzf001, gender=false, no=zzf001, password=123456, phone=18826****41, address=北京, status=1, deleted=false, departmentId=65684a126cd811ea802000fffc35d9fa, create=Wed Sep 04 21:54:49 CST 2019, modified=Wed Sep 04 21:54:51 CST 2019] 2020-03-30 20:40:48.872 cn.zzs.Mybatis.mapper.EmployeeMapper.insert - ==> Preparing: INSERT INTO demo_employee (id, name, gender, no, password , phone, address, status, deleted, department_id , gmt_create, gmt_modified) VALUES (?, ?, ?, ?, ? , ?, ?, ?, ?, ? , ?, ?) 2020-03-30 20:40:48.899 cn.zzs.Mybatis.mapper.EmployeeMapper.insert - ==> Parameters: 517cabff75b24129b54048ce7d3280f9(String), zzs005(String), true(Boolean), zzs005(String), admin(String), 18826****41(String), 廣東(String), 1(Byte), false(Boolean), 94e2d2e56cd811ea802000fffc35d9fa(String), 2020-03-30 20:40:47.808(Timestamp), 2020-03-30 20:40:47.808(Timestamp) 2020-03-30 20:40:48.991 cn.zzs.Mybatis.mapper.EmployeeMapper.insert - <== Updates: 1 ``` ### 補充--批量新增 這裡我再補充下批量新增的實現。只要在上面 insert 語句的基礎上增加一個 foreach 標籤就可以,非常方便。在本系列第二篇文章中我將說到這些動態 sql 的用法。 ```xml部門編號
*/ private String departmentNo; /** *部門名
*/ private String departmentName; public boolean isJoinDepartment() { return (departmentNo != null && !departmentNo.isEmpty()) || (departmentName != null && !departmentName.isEmpty()); } // 省略setter/getter方法 } ``` ### 編寫 mapper xml檔案 Mybatis 提供了豐富的動態 sql 語法,以下可以完成高階條件查詢的 sql 拼接。 ```java