之前學(xué)習(xí)MyBatis整理了一些筆記,筆記分為四個(gè)部分:
1.MyBatis應(yīng)用分析與實(shí)踐
2.MyBatis體系結(jié)構(gòu)與工作原理
3.MyBatis插件原理及Spring集成
4.手寫自己的MyBatis框架
希望能夠幫助到小伙伴們。
本節(jié)目標(biāo):
1、 了解 ORM 框架發(fā)展歷史,了解 MyBatis 特性
2、 掌握 MyBatis 編程式開發(fā)方法和核心對(duì)象
3、 掌握 MyBatis 核心配置含義
4、 掌握 MyBatis 的高級(jí)用法與擴(kuò)展方式
一,為什么要用 MyBatis
JDBC 連接數(shù)據(jù)庫
在 Java 程序里面去連接數(shù)據(jù)庫,最原始的辦法是使用 JDBC 的 API。我們先來回顧 一下使用 JDBC 的方式,我們是怎么操作數(shù)據(jù)庫的。
// 注冊(cè) JDBC 驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
// 打開連接
conn = DriverManager.getConnection(DB_URL, USER, PASSWORD);
// 執(zhí)行查詢
stmt = conn.createStatement();
String sql= "SELECT bid, name, author_id FROM blog";
ResultSet rs = stmt.executeQuery(sql);
// 獲取結(jié)果集
while(rs.next()){
int bid = rs.getInt("bid");
String name = rs.getString("name");
String authorId = rs.getString("author_id");
}
- 首先,我們?cè)?maven 中引入 MySQL 驅(qū)動(dòng)的依賴(JDBC 的包在 java.sql 中)。
- 第一步,注冊(cè)驅(qū)動(dòng)。
- 第二步,通過 DriverManager 獲取一個(gè) Connection,參數(shù)里 面填數(shù)據(jù)庫地址,用戶名和密碼。
- 第三步,我們通過 Connection 創(chuàng)建一個(gè) Statement 對(duì)象。
- 第四步,通過 Statement 的 execute()方法執(zhí)行 SQL。當(dāng)然 Statement 上面定義了 非常多的方法。execute()方法返回一個(gè) ResultSet 對(duì)象,我們把它叫做結(jié)果集。
- 第五步,我們通過 ResultSet 獲取數(shù)據(jù)。轉(zhuǎn)換成一個(gè) POJO 對(duì)象。 最后,我們要關(guān)閉數(shù)據(jù)庫相關(guān)的資源,包括 ResultSet、Statement、Connection, 它們的關(guān)閉順序和打開的順序正好是相反的。
這個(gè)就是我們通過 JDBC 的 API 去操作數(shù)據(jù)庫的方法,這個(gè)僅僅是一個(gè)查詢。如果 我們項(xiàng)目當(dāng)中的業(yè)務(wù)比較復(fù)雜,表非常多,各種操作數(shù)據(jù)庫的增刪改查的方法也比較多 的話,那么這樣代碼會(huì)重復(fù)出現(xiàn)很多次。
在每一段這樣的代碼里面,我們都需要自己去管理數(shù)據(jù)庫的連接資源,如果忘記寫 close()了,就可能會(huì)造成數(shù)據(jù)庫服務(wù)連接耗盡。
另外還有一個(gè)問題就是處理業(yè)務(wù)邏輯和處理數(shù)據(jù)的代碼是耦合在一起的。如果業(yè)務(wù) 流程復(fù)雜,跟數(shù)據(jù)庫的交互次數(shù)多,耦合在代碼里面的 SQL 語句就會(huì)非常多。如果要修改業(yè)務(wù)邏輯,或者修改數(shù)據(jù)庫環(huán)境(因?yàn)椴煌臄?shù)據(jù)庫 SQL 語法略有不同),這個(gè)工作 量是也是難以估計(jì)的。
還有就是對(duì)于結(jié)果集的處理,我們要把 ResultSet 轉(zhuǎn)換成 POJO 的時(shí)候,必須根據(jù) 字段屬性的類型一個(gè)個(gè)地去處理,寫這樣的代碼是非??菰锏模?/p>
int bid = rs.getInt("bid");
String name = rs.getString("name");
String authorId = rs.getString("author_id");
blog.setAuthorId(authorId);
blog.setBid(bid);
blog.setName(name);
也正是因?yàn)檫@樣,我們?cè)趯?shí)際工作中是比較少直接使用 JDBC 的。那么我們?cè)?Java 程序里面有哪些更加簡(jiǎn)單的操作數(shù)據(jù)庫的方式呢?
Apache DbUtils
https://commons.apache.org/proper/commons-dbutils/
DbUtils 解決的最核心的問題就是結(jié)果集的映射,可以把 ResultSet 封裝成 JavaBean。它是怎么做的呢?
首先 DbUtils 提供了一個(gè) QueryRunner 類,它對(duì)數(shù)據(jù)庫的增刪改查的方法進(jìn)行了封裝,那么我們操作數(shù)據(jù)庫就可以直接使用它提供的方法。
在 QueryRunner 的構(gòu)造函數(shù)里面,我們又可以傳入一個(gè)數(shù)據(jù)源,比如在這里我們 Hikari,這樣我們就不需要再去寫各種創(chuàng)建和釋放連接的代碼了。
queryRunner = new QueryRunner(dataSource)
那我們?cè)趺窗呀Y(jié)果集轉(zhuǎn)換成對(duì)象呢?比如實(shí)體類 Bean 或者 List 或者 Map?在 DbUtils 里面提供了一系列的支持泛型的 ResultSetHandler。
我們只要在 DAO 層調(diào)用 QueryRunner 的查詢方法,傳入這個(gè) Handler,它就可以 自動(dòng)把結(jié)果集轉(zhuǎn)換成實(shí)體類 Bean 或者 List
String sql = "select * from blog";
List<BlogDto> list = queryRunner.query(sql, new BeanListHandler<>(BlogDto.class))
沒有用過 DbUtils 的同學(xué),可以思考一下通過結(jié)果集到實(shí)體類的映射是怎么實(shí)現(xiàn)的? 也就是說,我只傳了一個(gè)實(shí)體類的類型,它怎么知道這個(gè)類型有哪些屬性,每個(gè)屬性是 什么類型?然后創(chuàng)建這個(gè)對(duì)象并且給這些字段賦值的?答案正是反射。
大家也可以去看一下源碼映證一下是不是這樣。 問題:輸出的結(jié)果中,authorId 為什么是空的?DbUtils 要求數(shù)據(jù)庫的字段跟對(duì)象 的屬性名稱完全一致,才可以實(shí)現(xiàn)自動(dòng)映射。
BlogDto{bid=3, name='MyBatis 源碼分析', authorId='null'
Spring JDBC
除了 DbUtils 之外,Spring 也對(duì)原生的 JDBC 進(jìn)行了封裝,并且給我們提供了一個(gè) 模板方法 JdbcTemplate,來簡(jiǎn)化我們對(duì)數(shù)據(jù)庫的操作。第一個(gè),我們不再需要去關(guān)心資源管理的問題。
第二個(gè),對(duì)于結(jié)果集的處理,Spring JDBC 也提供了一個(gè) RowMapper 接口,可以 把結(jié)果集轉(zhuǎn)換成 Java 對(duì)象。
看代碼:比如我們要把結(jié)果集轉(zhuǎn)換成 Employee 對(duì)象,就可以針對(duì)一個(gè) Employee 創(chuàng)建一個(gè) RowMapper 對(duì)象,實(shí)現(xiàn) RowMapper 接口,并且重寫 mapRow()方法。我們 在 mapRow()方法里面完成對(duì)結(jié)果集的處理。
public class EmployeeRowMapper implements RowMapper {
@Override
public Object mapRow(ResultSet resultSet, int i) throws SQLException {
Employee employee = new Employee();
employee.setEmpId(resultSet.getInt("emp_id"));
employee.setEmpName(resultSet.getString("emp_name"));
employee.setEmail(resultSet.getString("emial"));
return employee;
}
}
在 DAO 層調(diào)用的時(shí)候就可以傳入自定義的 RowMapper 類,最終返回我們需要的類型。結(jié)果集和實(shí)體類類型的映射也是自動(dòng)完成的。
public List<Employee> query(String sql){
new JdbcTemplate( new DruidDataSource());
return jdbcTemplate.query(sql,new EmployeeRowMapper());
}
通過這種方式,我們對(duì)于結(jié)果集的處理只需要寫一次代碼,然后在每一個(gè)需要映射 的地方傳入這個(gè) RowMapper 就可以了,減少了很多的重復(fù)代碼。
但是還是有問題:每一個(gè)實(shí)體類對(duì)象,都需要定義一個(gè) Mapper,然后要編寫每個(gè)字段映射的 getString()、getInt 這樣的代碼,還增加了類的數(shù)量。
所以有沒有辦法讓一行數(shù)據(jù)的字段,跟實(shí)體類的屬性自動(dòng)對(duì)應(yīng)起來,實(shí)現(xiàn)自動(dòng)映射呢?當(dāng)然,我們肯定要解決兩個(gè)問題,一個(gè)就是名稱對(duì)應(yīng)的問題,從下劃線到駝峰命名;
第二個(gè)是類型對(duì)應(yīng)的問題,數(shù)據(jù)庫的 JDBC 類型和 Java 對(duì)象的類型要匹配起來。
我們可以創(chuàng)建一個(gè) BaseRowMapper,通過反射的方式自動(dòng)獲取所有屬性,把表字段全部賦值到屬性。
上面的方法就可以改成:
return jdbcTemplate.query(sql,new BaseRowMapper(Employee.class));
這樣,我們?cè)谑褂玫臅r(shí)候只要傳入我們需要轉(zhuǎn)換的類型就可以了,不用再單獨(dú)創(chuàng)建 一個(gè) RowMapper.
我們來總結(jié)一下,DbUtils 和 Spring JDBC,這兩個(gè)對(duì) JDBC 做了輕量級(jí)封裝的框架, 或者說工具類里面,都幫助我們解決了一些問題:
- 無論是 QueryRunner 還是 JdbcTemplate,都可以傳入一個(gè)數(shù)據(jù)源進(jìn)行初始化,也就是資源管理這一部分的事情,可以交給專門的數(shù)據(jù)源組件去做,不用我們手動(dòng)創(chuàng)建和關(guān)閉;
- 對(duì)操作數(shù)據(jù)的增刪改查的方法進(jìn)行了封裝;
- 可以幫助我們映射結(jié)果集,無論是映射成 List、Map 還是實(shí)體類。 但是還是存在一些缺點(diǎn):
- SQL 語句都是寫死在代碼里面的,依舊存在硬編碼的問題;
- 參數(shù)只能按固定位置的順序傳入(數(shù)組),它是通過占位符去替換的, 不能自動(dòng)映射;
- 在方法里面,可以把結(jié)果集映射成實(shí)體類,但是不能直接把實(shí)體類映射成數(shù)據(jù)庫的記錄(沒有自動(dòng)生成 SQL 的功能);
- 查詢沒有緩存的功能;
Hibernate
要解決這些問題,使用這些工具類還是不夠的,要用到我們今天講的 ORM 框架。
那什么是 ORM?為什么叫 ORM?
ORM 的全拼是 Object Relational Mapping,也就是對(duì)象與關(guān)系的映射,對(duì)象是程 序里面的對(duì)象,關(guān)系是它與數(shù)據(jù)庫里面的數(shù)據(jù)的關(guān)系。也就是說,ORM 框架幫助我們解 決的問題是程序?qū)ο蠛完P(guān)系型數(shù)據(jù)庫的相互映射的問題。
O:對(duì)象——M:映射——R:關(guān)系型數(shù)據(jù)庫

現(xiàn)在應(yīng)該有很多同學(xué)是用過 Hibernate 或者現(xiàn)在還在用的。Hibernate 是一個(gè)很流行的 ORM 框架,2001 年的時(shí)候就出了第一個(gè)版本。在使用 Hibernate 的時(shí) 候,我們需要為實(shí)體類建立一些 hbm 的 xml 映射文件(或者類似于@Table 的這樣的注解)。例如:
<hibernate-mapping>
<class name="cn.gupaoedu.vo.User" table="user">
<id name="id">
<generator class="native"/>
</id>
<property name="password"/>
<property name="cellphone"/>
<property name="username"/>
</class>
</hibernate-mapping>
然后通過 Hibernate 提供(session)的增刪改查的方法來操作對(duì)象.
//創(chuàng)建對(duì)象
User user = new User();
user.setPassword("123456");
user.setCellphone("18166669999");
user.setUsername("qingshan");
//獲取加載配置管理類
Configuration configuration = new Configuration();
//不給參數(shù)就默認(rèn)加載 hibernate.cfg.xml 文件,
configuration.configure();
//創(chuàng)建 Session 工廠對(duì)象
SessionFactory factory = configuration.buildSessionFactory();
//得到 Session 對(duì)象
Session session = factory.openSession();
//使用 Hibernate 操作數(shù)據(jù)庫,都要開啟事務(wù),得到事務(wù)對(duì)象
Transaction transaction = session.getTransaction();
//開啟事務(wù)
transaction.begin();
//把對(duì)象添加到數(shù)據(jù)庫中
session.save(user);
//提交事務(wù)
transaction.commit();
//關(guān)閉 Session
session.close();
我們操作對(duì)象就跟操作數(shù)據(jù)庫的數(shù)據(jù)一樣。Hibernate 的框架會(huì)自動(dòng)幫我們生成SQL 語句(可以屏蔽數(shù)據(jù)庫的差異),自動(dòng)進(jìn)行映射。這樣我們的代碼變得簡(jiǎn)潔了,程序的可讀性也提高了.
但是 Hibernate 在業(yè)務(wù)復(fù)雜的項(xiàng)目中使用也存在一些問題:
- 比如使用 get()、save() 、update()對(duì)象的這種方式,實(shí)際操作的是所有字段,沒有辦法指定部分字段,換句話說就是不夠靈活。
- 這種自動(dòng)生成 SQL 的方式,如果我們要去做一些優(yōu)化的話,是非常困難的,也就是說可能會(huì)出現(xiàn)性能比較差的問題。
- 不支持動(dòng)態(tài) SQL(比如分表中的表名變化,以及條件、參數(shù))。
MyBatis
“半自動(dòng)化”的 ORM 框架 MyBatis 就解決了這幾個(gè)問題?!鞍胱詣?dòng)化”是相對(duì)于 Hibernate 的全自動(dòng)化來說的,也就是說它的封裝程度沒有 Hibernate 那么高,不會(huì)自動(dòng)生成全部的 SQL 語句,主要解決的是 SQL 和對(duì)象的映射問題。
在 MyBatis 里面,SQL 和代碼是分離的,所以會(huì)寫 SQL 基本上就會(huì)用 MyBatis,沒有額外的學(xué)習(xí)成本。
我們來總結(jié)一下,MyBatis 的核心特性,或者說它解決的主要問題是什么:
- 使用連接池對(duì)連接進(jìn)行管理
- SQL 和代碼分離,集中管理
- 結(jié)果集映射
- 參數(shù)映射和動(dòng)態(tài) SQL
- 重復(fù) SQL 的提取
- 緩存管理
- 插件機(jī)
當(dāng)然,需要明白的是,Hibernate 和 MyBatis 跟 DbUtils、Spring JDBC 一樣,都是對(duì) JDBC 的一個(gè)封裝,我們?nèi)タ丛创a,最后一定會(huì)看到 Statement 和 ResultSet 這些 對(duì)象。
問題來了,我們有這么多的工具和不同的框架,在實(shí)際的項(xiàng)目里面應(yīng)該怎么選擇?
在一些業(yè)務(wù)比較簡(jiǎn)單的項(xiàng)目中,我們可以使用 Hibernate;
如果需要更加靈活的 SQL,可以使用 MyBatis,對(duì)于底層的編碼,或者性能要求非 常高的場(chǎng)合,可以用 JDBC。
實(shí)際上在我們的項(xiàng)目中,MyBatis 和 Spring JDBC 是可以混合使用的。
當(dāng)然,我們也根據(jù)項(xiàng)目的需求自己寫 ORM 框架。
二,MyBatis實(shí)戰(zhàn)
編程式
大部分時(shí)候,我們都是在 Spring 里面去集成 MyBatis。因?yàn)?Spring 對(duì) MyBatis 的 一些操作進(jìn)行的封裝,我們不能直接看到它的本質(zhì),所以先看下不使用容器的時(shí)候,也就是編程的方式,MyBatis 怎么使用。
先引入 mybatis jar 包。
首先我們要?jiǎng)?chuàng)建一個(gè)全局配置文件,這里面是對(duì) MyBatis 的核心行為的控制,比如 mybatis-config.xml。
第二個(gè)就是我們的映射器文件,Mapper.xml,通常來說一張表對(duì)應(yīng)一個(gè),我們會(huì)在 這個(gè)里面配置我們?cè)鰟h改查的 SQL 語句,以及參數(shù)和返回的結(jié)果集的映射關(guān)系。
跟 JDBC 的代碼一樣,我們要執(zhí)行對(duì)數(shù)據(jù)庫的操作,必須創(chuàng)建一個(gè)會(huì)話,這個(gè)在 MyBatis 里面就是 SqlSession。SqlSession 又是工廠類根據(jù)全局配置文件創(chuàng)建的。所以整個(gè)的流程就是這樣的(如下代碼)。最后我們通過 SqlSession 接口上的方法,傳入我 們的 Statement ID 來執(zhí)行 SQL。這是第一種方式。
這種方式有一個(gè)明顯的缺點(diǎn),就是會(huì)對(duì) Statement ID 硬編碼,而且不能在編譯時(shí)進(jìn)行類型檢查,所以通常我們會(huì)使用第二種方式,就是定義一個(gè) Mapper 接口的方式。這 個(gè)接口全路徑必須跟 Mapper.xml 里面的 namespace 對(duì)應(yīng)起來,方法也要跟 Statement ID 一一對(duì)應(yīng)
public void testMapper() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlogById(1);
System.out.println(blog);
} finally {
session.close();
}
}
這個(gè)就是我們單獨(dú)使用 MyBatis 的全部流程。
核心對(duì)象的生命周期
在編程式使用的這個(gè) demo 里面,我們看到了 MyBatis 里面的幾個(gè)核心對(duì)象: SqlSessionFactoryBuiler、SqlSessionFactory、SqlSession 和 Mapper 對(duì)象。這幾個(gè) 核心對(duì)象在 MyBatis 的整個(gè)工作流程里面的不同環(huán)節(jié)發(fā)揮作用。如果說我們不用容器,自己去管理這些對(duì)象的話,我們必須思考一個(gè)問題:什么時(shí)候創(chuàng)建和銷毀這些對(duì)象?
在一些分布式的應(yīng)用里面,多線程高并發(fā)的場(chǎng)景中,如果要寫出高效的代碼,必須了解這四個(gè)對(duì)象的生命周期。這四個(gè)對(duì)象的聲明周期的描述在官網(wǎng)上面也可以找到。 http://www.mybatis.org/mybatis-3/zh/getting-started.html.
我們從每個(gè)對(duì)象的作用的角度來理解一下,只有理解了它們是干什么的,才知道什么時(shí)候應(yīng)該創(chuàng)建,什么時(shí)候應(yīng)該銷毀.
-
SqlSessionFactoryBuiler
首先是 SqlSessionFactoryBuiler , 它 是用來構(gòu)建 SqlSessionFactory 的 ,而SqlSessionFactory只需要一個(gè),所以只要構(gòu)建了這一個(gè) SqlSessionFactory,它的使命就完成了,也就沒有存在的意義了。所以它的生命周期只存在于方法的局部。
-
SqlSessionFactory
SqlSessionFactory 是用來創(chuàng)建 SqlSession 的,每次應(yīng)用程序訪問數(shù)據(jù)庫,都需要?jiǎng)?chuàng)建一個(gè)會(huì)話。因?yàn)槲覀円恢庇袆?chuàng)建會(huì)話的需要,所以SqlSessionFactory 應(yīng)該存在于應(yīng)用的整個(gè)生命周期中(作用域是應(yīng)用作用域)。創(chuàng)建 SqlSession 只需要一個(gè)實(shí)例來做 這件事就行了,否則會(huì)產(chǎn)生很多的混亂,和浪費(fèi)資源。所以我們要采用單例模式.
-
SqlSession
SqlSession 是一個(gè)會(huì)話,因?yàn)樗皇蔷€程安全的,不能在線程間共享。所以我們?cè)谡?qǐng)求開始的時(shí)候創(chuàng)建一個(gè) SqlSession 對(duì)象,在請(qǐng)求結(jié)束或者說方法執(zhí)行完畢的時(shí)候要及時(shí)關(guān)閉它(一次請(qǐng)求或者操作中).
-
Mapper
Mapper(實(shí)際上是一個(gè)代理對(duì)象)是從 SqlSession 中獲取的。
BlogMapper mapper = session.getMapper(BlogMapper.class);它的作用是發(fā)送 SQL 來操作數(shù)據(jù)庫的數(shù)據(jù)。它應(yīng)該在一個(gè) SqlSession 事務(wù)方法之內(nèi).
最后總結(jié)如下:
| 對(duì)象 | 生命周期 |
|---|---|
| SqlSessionFactoryBuiler | 方法局部(method) |
| SqlSessionFactory(單例) | 應(yīng)用級(jí)別(application) |
| SqlSession | 請(qǐng)求和操作(request/method) |
| Mapper | 方法(method) |
這個(gè)就是我們?cè)诰幊淌降氖褂美锩婵吹降乃膫€(gè)對(duì)象的生命周期的總結(jié)。
核心配置解讀
第一個(gè)是 config 文件。大部分時(shí)候我們只需要很少的配置就可以讓 MyBatis 運(yùn)行起 來。其實(shí) MyBatis 里面提供的配置項(xiàng)非常多,我們沒有配置的時(shí)候使用的是系統(tǒng)的默認(rèn)值。
mybatis-3 的 源 碼 托 管 在 github 上 。 源 碼 地 址 https://github.com/mybatis/mybatis-3/releases
目前最新的版本是 3.5.1,大家可以從官方上下載到最新的源碼。
第一個(gè)是 jar 包和文檔。第二個(gè)第三個(gè)是源碼
在這個(gè)壓縮包里面,解壓出來有一個(gè) mybatis-3.5.1.pdf,是英文版本的。如果閱讀 英文困難,可以基于 3.5.1 的中文版本學(xué):
http://www.mybatis.org/mybatis-3/zh/index.html
一級(jí)標(biāo)簽
-
configuration
configuration 是整個(gè)配置文件的根標(biāo)簽,實(shí)際上也對(duì)應(yīng)著 MyBatis 里面最重要的 配置類 Configuration。它貫穿 MyBatis 執(zhí)行流程的每一個(gè)環(huán)節(jié)。我們打開這個(gè)類看一 下,這里面有很多的屬性,跟其他的子標(biāo)簽也能對(duì)應(yīng)上。
注意:MyBatis 全局配置文件順序是固定的,否則啟動(dòng)的時(shí)候會(huì)報(bào)錯(cuò)。
-
properties
第一個(gè)是 properties 標(biāo)簽,用來配置參數(shù)信息,比如最常見的數(shù)據(jù)庫連接信息。
為了避免直接把參數(shù)寫死在 xml 配置文件中,我們可以把這些參數(shù)單獨(dú)放在 properties 文件中,用 properties 標(biāo)簽引入進(jìn)來,然后在 xml 配置文件中用${}引用就 可以了。
可以用 resource 引用應(yīng)用里面的相對(duì)路徑,也可以用 url 指定本地服務(wù)器或者網(wǎng)絡(luò)的絕對(duì)路徑。 我們?yōu)槭裁匆堰@些配置獨(dú)立出來?有什么好處?或者說,公司的項(xiàng)目在打包的時(shí)候,有沒有把 properties 文件打包進(jìn)去?
1、 提取,利于多處引用,維護(hù)簡(jiǎn)單;
2、 把配置文件放在外部,避免修改后重新編譯打包,只需要重啟應(yīng)用;
3、 程序和配置分離,提升數(shù)據(jù)的安全性,比如生產(chǎn)環(huán)境的密碼只有運(yùn)維人員掌握。
-
settings
setttings 里面是 MyBatis 的一些核心配置,我們最后再看,先看下其他的以及標(biāo)簽。
-
typeAliases
TypeAlias 是類型的別名,跟 Linux 系統(tǒng)里面的 alias 一樣,主要用來簡(jiǎn)化全路徑類 名的拼寫。比如我們的參數(shù)類型和返回值類型都可能會(huì)用到我們的 Bean,如果每個(gè)地方都配置全路徑的話,那么內(nèi)容就比較多,還可能會(huì)寫錯(cuò)。
我們可以為自己的 Bean 創(chuàng)建別名,既可以指定單個(gè)類,也可以指定一個(gè) package, 自動(dòng)轉(zhuǎn)換。配置了別名以后,只需要寫別名就可以了,比如 com.javacoo.domain.Person都只要寫 person就可以了。
MyBatis 里面有系統(tǒng)預(yù)先定義好的類型別名,在 TypeAliasRegistry 中。
-
typeHandlers【重點(diǎn)】
由于 Java 類型和數(shù)據(jù)庫的 JDBC 類型不是一一對(duì)應(yīng)的(比如 String 與 varchar), 所以我們把 Java 對(duì)象轉(zhuǎn)換為數(shù)據(jù)庫的值,和把數(shù)據(jù)庫的值轉(zhuǎn)換成 Java 對(duì)象,需要經(jīng)過一定的轉(zhuǎn)換,這兩個(gè)方向的轉(zhuǎn)換就要用到 TypeHandler。
有的同學(xué)可能會(huì)有疑問,我沒有做任何的配置,為什么實(shí)體類對(duì)象里面的一個(gè)String 屬性,可以保存成數(shù)據(jù)庫里面的 varchar 字段,或者保存成 char 字段? 這是因?yàn)?MyBatis 已經(jīng)內(nèi)置了很多 TypeHandler(在 type 包下),它們?nèi)咳?注冊(cè)在 TypeHandlerRegistry 中,他們都繼承了抽象類 BaseTypeHandler,泛型就是處理的 Java 數(shù)據(jù)類型.

當(dāng)我們做數(shù)據(jù)類型轉(zhuǎn)換的時(shí)候,就會(huì)自動(dòng)調(diào)用對(duì)應(yīng)的 TypeHandler 的方法。
如果我們需要自定義一些類型轉(zhuǎn)換規(guī)則,或者要在處理類型的時(shí)候做一些特殊的動(dòng) 作,就可以編寫自己的 TypeHandler,跟系統(tǒng)自定義的 TypeHandler 一樣,繼承抽象類 BaseTypeHandler。有 4 個(gè)抽象方法必須實(shí)現(xiàn),我們把它分成兩類:
set 方法從 Java 類型轉(zhuǎn)換成 JDBC 類型的,get 方法是從 JDBC 類型轉(zhuǎn)換成 Java 類 型的。
| 從 Java 類型到 JDBC 類型 | 從 JDBC 類型到 Java 類型 |
|---|---|
| setNonNullParameter:設(shè)置非空參數(shù) | getNullableResult:獲取空結(jié)果集(根據(jù)列名),一般都是調(diào)用這個(gè) <br />getNullableResult:獲取空結(jié)果集(根據(jù)下標(biāo)值) <br />getNullableResult:存儲(chǔ)過程用的 |
比如我們想要在獲取或者設(shè)置 String 類型的時(shí)候做一些特殊處理,我們可以寫一個(gè) String 類型的 TypeHandler(mybatis-standalone 工程)。
public class MyTypeHandler extends BaseTypeHandler<String> {
public void setNonNullParameter(PreparedStatement ps, int i, String parameter,JdbcType jdbcType)
throws SQLException {
// 設(shè)置 String 類型的參數(shù)的時(shí)候調(diào)用,Java 類型到 JDBC 類型
System.out.println("---------------setNonNullParameter1:"+parameter);
ps.setString(i, parameter);
}
public String getNullableResult(ResultSet rs, String columnName) throws SQLException{
// 根據(jù)列名獲取 String 類型的參數(shù)的時(shí)候調(diào)用,JDBC 類型到 java 類型
System.out.println("---------------getNullableResult1:"+columnName);
return rs.getString(columnName);
}
// 后面兩個(gè)方法省略…………
}
第二步,在 mybatis-config.xml 文件中注冊(cè)
<typeHandlers>
<typeHandler handler="com.javacoo.type.MyTypeHandler"></typeHandler>
</typeHandlers>
第三步,在我們需要使用的字段上指定,比如:
插入值的時(shí)候,從 Java 類型到 JDBC 類型,在字段屬性中指定 typehandler:
<insert id="insertBlog" parameterType="com.javacoo.domain.Blog">
insert into blog (bid, name, author_id)
values (#{bid,jdbcType=INTEGER},
#{name,jdbcType=VARCHAR,typeHandler=com.gupaoedu.type.MyTypeHandler},
#{authorId,jdbcType=INTEGER})
</insert>
返回值的時(shí)候,從 JDBC 類型到 Java 類型,在 resultMap 的列上指定 typehandler:
<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.javacoo.type.MyTypeHandler"/>
【思考】
如果我們的對(duì)象里面有復(fù)雜對(duì)象,比如 Blog 里面包括了一個(gè) Comment 對(duì)象,這個(gè)時(shí)候 Comment 對(duì)象的全部屬性不能直接映射到數(shù)據(jù)庫的一個(gè)字段。
要求:創(chuàng)建一個(gè) TypeHandler,可以將任意的對(duì)象轉(zhuǎn)換為 json 字符串,保存到數(shù)據(jù)庫的 VARCHAR 類型中。在從數(shù)據(jù)庫查詢的時(shí)候,再轉(zhuǎn)換為原來的 Java 對(duì)象。
1、 在數(shù)據(jù)庫表添加一個(gè) VARCHAR 字段;
2、 在 Blog 對(duì)象中添加一個(gè) Comment 屬性,字段 Integer id;String content;
3、 JSON 工具沒有要求,jackson 或者 fastjson、gson 都可以。
4、 在查詢和插入的 statement 上使用這個(gè) TypeHandler。
-
objectFactory【重點(diǎn)】
當(dāng)我們把數(shù)據(jù)庫返回的結(jié)果集轉(zhuǎn)換為實(shí)體類的時(shí)候,需要?jiǎng)?chuàng)建對(duì)象的實(shí)例,由于我們不知道需要處理的類型是什么,有哪些屬性,所以不能用 new 的方式去創(chuàng)建。在 MyBatis里面,它提供了一個(gè)工廠類的接口,叫做 ObjectFactory,專門用來創(chuàng)建對(duì)象的 實(shí)例,里面定義了 4個(gè)方法。

| 方法 | 作用 |
|---|---|
| void setProperties(Properties properties); | 設(shè)置參數(shù)時(shí)調(diào)用 |
| <T> T create(Class<T> type) | 創(chuàng)建對(duì)象(調(diào)用無參構(gòu)造函數(shù)) |
| <T> T create(Class<T> type, List> constructorArgTypes, List constructorArgs); | 創(chuàng)建對(duì)象(調(diào)用帶參數(shù)構(gòu)造函數(shù)) |
| <T> boolean isCollection(Class<T> type) | 判斷是否集合 |
ObjectFactory 有一個(gè)默認(rèn)的實(shí)現(xiàn)類 DefaultObjectFactory,創(chuàng)建對(duì)象的方法最終 都調(diào)用了 instantiateClass(),是通過反射來。
如果想要修改對(duì)象工廠在初始化實(shí)體類的時(shí)候的行為,就可以通過創(chuàng)建自己的對(duì)象 工廠,繼承 DefaultObjectFactory 來實(shí)現(xiàn)(不需要再實(shí)現(xiàn) ObjectFactory 接口),例如:
public class MyObjectFactory extends DefaultObjectFactory {
@Override
public Object create(Class type) {
if (type.equals(Blog.class)) {
Blog blog = (Blog) super.create(type);
blog.setName("by object factory");
blog.setBid(1111);
blog.setAuthorId(2222);
return blog;
}
Object result = super.create(type);
return result;
}
}
我們可以直接用自定義的工廠類來創(chuàng)建對(duì)象:
public class ObjectFactoryTest {
public static void main(String[] args) {
MyObjectFactory factory = new MyObjectFactory();
Blog myBlog = (Blog) factory.create(Blog.class);
System.out.println(myBlog);
}
}
這樣我們就直接拿到了一個(gè)對(duì)象。
如果在 config 文件里面注冊(cè),在創(chuàng)建對(duì)象的時(shí)候會(huì)被自動(dòng)調(diào)用:
<objectFactory type="org.mybatis.example.MyObjectFactory">
<!-- 對(duì)象工廠注入的參數(shù) -->
<property name="javacoo" value="666"/>
</objectFactory>
這樣,就可以讓 MyBatis 的創(chuàng)建實(shí)體類的時(shí)候使用我們自己的對(duì)象工廠。
應(yīng)用場(chǎng)景舉例:
比如有一個(gè)新銳手機(jī)品牌在一個(gè)電商平臺(tái)上面賣貨,為了讓預(yù)約數(shù)量好看一點(diǎn),只要有人預(yù)約,預(yù)約數(shù)量就自動(dòng)乘以 3。這個(gè)時(shí)候就可以創(chuàng)建一個(gè) ObjectFactory,只要是查詢銷量,就把它的預(yù)約數(shù)乘以 3,被發(fā)現(xiàn)后,平臺(tái):是程序員干的。
1、什么時(shí)候調(diào)用了 objectFactory.create()? 創(chuàng)建 DefaultResultSetHandler 的時(shí)候,和創(chuàng)建對(duì)象的時(shí)候。
2、創(chuàng)建對(duì)象后,已有的屬性為什么被覆蓋了? 在 DefaultResultSetHandler 類的 395 行 getRowValue()方法里面里面調(diào)用了 applyPropertyMappings()。 3、返回結(jié)果的時(shí)候,ObjectFactory 和 TypeHandler 哪個(gè)先工作? 先是 ObjectFactory,再是 TypeHandler??隙ㄊ窍葎?chuàng)建對(duì)象。 PS:step out 可以看到一步步調(diào)用的層級(jí)。
-
plugins
插件是 MyBatis 的一個(gè)很強(qiáng)大的機(jī)制,跟很多其他的框架一樣,MyBatis 預(yù)留了插 件的接口,讓 MyBatis 更容易擴(kuò)展。
根據(jù)官方的定義,插件可以攔截這四個(gè)對(duì)象的這些方法,我們把這四個(gè)對(duì)象稱作 MyBatis 的四大對(duì)象。閱讀源碼,知道了這 4 大對(duì)象的作用之后,再來分析自定義插件的開發(fā)和插件運(yùn)行的原理。 http://www.mybatis.org/mybatis-3/zh/configuration.html#plugins
類(或接口) 方法 Executor update, query, flushStatements, commit, rollback, getTransaction, close, isClosed ParameterHandler getParameterObject, setParameters ResultSetHandler handleResultSets, handleOutputParameters StatementHandler prepare, parameterize, batch, update, query -
environments、environment
environments 標(biāo)簽用來管理數(shù)據(jù)庫的環(huán)境,比如我們可以有開發(fā)環(huán)境、測(cè)試環(huán)境、 生產(chǎn)環(huán)境的數(shù)據(jù)庫。可以在不同的環(huán)境中使用不同的數(shù)據(jù)庫地址或者類型。
<environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://127.0.0.1:3306/jc-mybatis?useUnicode=true"/> <property name="username" value="root"/> <property name="password" value="123456"/> </dataSource> </environment> </environments>一個(gè) environment 標(biāo)簽就是一個(gè)數(shù)據(jù)源,代表一個(gè)數(shù)據(jù)庫。這里面有兩個(gè)關(guān)鍵的標(biāo) 簽,一個(gè)是事務(wù)管理器,一個(gè)是數(shù)據(jù)源。
-
transactionManager
如果配置的是 JDBC,則會(huì)使用 Connection 對(duì)象的 commit()、rollback()、close() 管理事務(wù)。
如果配置成 MANAGED,會(huì)把事務(wù)交給容器來管理,比如 JBOSS,Weblogic。因 為我們跑的是本地程序,如果配置成 MANAGE 不會(huì)有任何事務(wù)。
如果是 Spring + MyBatis , 則 沒 有 必 要 配 置 , 因?yàn)槲覀儠?huì)直接在 applicationContext.xml 里面配置數(shù)據(jù)源,覆蓋 MyBatis 的配置。
-
dataSource
將在下一節(jié)(settings)詳細(xì)分析。在跟 Spring 集成的時(shí)候,事務(wù)和數(shù)據(jù)源都會(huì)交 給 Spring 來管理
-
mappers
標(biāo)簽配置的是我們的映射器,也就是 Mapper.xml 的路徑。這里配置的目的是讓 MyBatis 在啟動(dòng)的時(shí)候去掃描這些映射器,創(chuàng)建映射關(guān)系。
我們有四種指定 Mapper 文件的方式: http://www.mybatis.org/mybatis-3/zh/configuration.html#mappers
1、使用相對(duì)于類路徑的資源引用(resource)
2、使用完全限定資源定位符(絕對(duì)路徑)(URL)
3、使用映射器接口實(shí)現(xiàn)類的完全限定類名
4、將包內(nèi)的映射器接口實(shí)現(xiàn)全部注冊(cè)為映射器(最常用)
思考:
接口跟 statement 是怎么綁定起來的?
——method 有方法全限定名,比如: com.javacoo.mapper.BlogMapper.selectBlogById , 跟 namespace 里 面 的 statement ID 是相同
在哪一步拿到 SQL 的?
——ms 里面有 SQL
MappedStatement ms = configuration.getMappedStatement(statement); -
settings
最后 settings 我們來單獨(dú)說一下,因?yàn)?MyBatis 的一些最關(guān)鍵的配置都在這個(gè)標(biāo)簽 里面(只講解一些主要的)。
屬性名 含義 簡(jiǎn)介 有效值 默認(rèn)值 cacheEnabled 是否使用緩存 是整個(gè)工程中所有映射器配置緩存的 開關(guān),即是一個(gè)全局緩存開關(guān) true/false true lazyLoadingEnabled 是否開啟延遲加載 控制全局是否使用延遲加載 (association、collection)。當(dāng)有 特殊關(guān)聯(lián)關(guān)系需要單獨(dú)配置時(shí),可以使 用 fetchType 屬性來覆蓋此配置 true/false false aggressiveLazyLoading 是否需要侵入式延遲加載 開啟時(shí),無論調(diào)用什么方法加載某個(gè)對(duì) 象,都會(huì)加載該對(duì)象的所有屬性,關(guān)閉 之后只會(huì)按需加載 true/false false defaultExecutorType 設(shè)置默認(rèn)的執(zhí)行器 有三種執(zhí)行器:SIMPLE 為普通執(zhí)行器; REUSE 執(zhí)行器會(huì)重用與處理語句; BATCH 執(zhí)行器將重用語句并執(zhí)行批量 更新 SIMPLE/REUSE/BAT CH SIMPLE lazyLoadTriggerMethods 指定哪個(gè)對(duì)象的方 法觸發(fā)一次延遲加 載 配置需要觸發(fā)延遲加載的方法的名字, 該方法就會(huì)觸發(fā)一次延遲加載 一個(gè)逗號(hào)分隔的方 法名稱列表 equals, clone, hashCode, toString localCacheScopeMyBatis 利用本地 緩存機(jī)制(Local Cache)防止循環(huán)引 用(circular references)和加 速重復(fù)嵌套查詢 默認(rèn)值為 SESSION,這種情況下會(huì)緩存 一個(gè)會(huì)話中執(zhí)行的所有查詢。若設(shè)置值 為 STATEMENT,本地會(huì)話僅用在語句執(zhí) 行上,對(duì)相同 SqlSession 的不同調(diào)用 將不會(huì)共享數(shù)據(jù) SESSION/STATEMENT SESSION logImpl 日志實(shí)現(xiàn) 指定 MyBatis 所用日志的具體實(shí)現(xiàn),未 指定時(shí)將自動(dòng)查找 SLF4J、LOG4J、 LOG4J2、 JDK_LOGGING、 COMMONS_LOGGING、 STDOUT_LOGGING、 NO_LOGGING 無
-
Mapper.xml 映射配置文件【重點(diǎn)】
http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html
映射器里面最主要的是配置了 SQL 語句,也解決了我們的參數(shù)映射和結(jié)果集映射的 問題。
一共有 8 個(gè)標(biāo)簽:
cache – 給定命名空間的緩存配置(是否開啟二級(jí)緩存)。
cache-ref – 其他命名空間緩存配置的引用。這兩個(gè)標(biāo)簽我們?cè)谥v解緩存的時(shí)候會(huì)詳細(xì)講到。
resultMap – 是最復(fù)雜也是最強(qiáng)大的元素,用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對(duì)象。
<resultMap id="BaseResultMap" type="Employee"> <id column="emp_id" jdbcType="INTEGER" property="empId"/> <result column="emp_name" jdbcType="VARCHAR" property="empName"/> <result column="gender" jdbcType="CHAR" property="gender"/> <result column="email" jdbcType="VARCHAR" property="email"/> <result column="d_id" jdbcType="INTEGER" property="dId"/> </resultMap>sql – 可被其他語句引用的可重用語句塊。
<sql id="Base_Column_List"> emp_id, emp_name, gender, email, d_id </sql>增刪改查標(biāo)簽:
insert – 映射插入語句
update – 映射更新語句
delete – 映射刪除語句
select – 映射查詢語
-
總結(jié)
配置名稱 配置含義 配置簡(jiǎn)介 configuration 包裹所有配置標(biāo)簽 整個(gè)配置文件的頂級(jí)標(biāo)簽 properties 屬性 該標(biāo)簽可以引入外部配置的屬性,也可以自己配置。該配置標(biāo)簽所 在的同一個(gè)配置文件的其他配置均可以引用此配置中的屬性 setting 全局配置參數(shù) 用來配置一些改變運(yùn)行時(shí)行為的信息,例如是否使用緩存機(jī)制,是 否使用延遲加載,是否使用錯(cuò)誤處理機(jī)制等。 typeAliases 類型別名 用來設(shè)置一些別名來代替 Java 的長(zhǎng)類型聲明(如 java.lang.int 變?yōu)?int),減少配置編碼的冗余 typeHandlers 類型處理器 將數(shù)據(jù)庫獲取的值以合適的方式轉(zhuǎn)換為 Java 類型,或者將 Java 類型的參數(shù)轉(zhuǎn)換為數(shù)據(jù)庫對(duì)應(yīng)的類型 objectFactory 對(duì)象工廠 實(shí)例化目標(biāo)類的工廠類配置 environments 環(huán)境集合屬性對(duì)象 數(shù)據(jù)庫環(huán)境信息的集合。在一個(gè)配置文件中,可以有多種數(shù)據(jù)庫環(huán) 境集合,這樣可以使 MyBatis 將 SQL 同時(shí)映射至多個(gè)數(shù)據(jù)庫 environment 環(huán)境子屬性對(duì)象 數(shù)據(jù)庫環(huán)境配置的詳細(xì)配置 transactionManager 事務(wù)管理 指定 MyBatis 的事務(wù)管理器 dataSource 數(shù)據(jù)源 使用其中的 type 指定數(shù)據(jù)源的連接類型,在標(biāo)簽對(duì)中可以使用 property 屬性指定數(shù)據(jù)庫連接池的其他信息 mappers 映射器 配置 SQL 映射文件的位置,告知 MyBatis 去哪里加載 SQL 映射文件
三,MyBatis 最佳實(shí)踐
以下是一些 MyBatis 的高級(jí)用法或者擴(kuò)展方式,幫助我們更好地使用 MyBatis。
動(dòng)態(tài) SQL
為什么需要?jiǎng)討B(tài) SQL
由于前臺(tái)傳入的查詢參數(shù)不同,所以寫了很多的 if else,還需要非常注意 SQL 語句 里面的 and、空格、逗號(hào)和轉(zhuǎn)移的單引號(hào)這些,拼接和調(diào)試 SQL 就是一件非常耗時(shí)的工 作。 MyBaits 的動(dòng)態(tài) SQL 就幫助我們解決了這個(gè)問題,它是基于 OGNL 表達(dá)式的。
動(dòng)態(tài)標(biāo)簽有哪些?
按照官網(wǎng)的分類,MyBatis 的動(dòng)態(tài)標(biāo)簽主要有四類:if,choose (when, otherwise), trim (where, set),foreach
-
if —— 需要判斷的時(shí)候,條件寫在 test 中 以下語句可以用改寫
<select id="selectDept" parameterType="int" resultType="com.javacoo.crud.bean.Department"> select * from tbl_dept where 1=1 <if test="deptId != null"> and dept_id = #{deptId,jdbcType=INTEGER} </if> </select> -
choose (when, otherwise) —— 需要選擇一個(gè)條件的時(shí)
<select id="getEmpList_choose" resultMap="empResultMap" parameterType="com.javacoo.crud.bean.Employee"> SELECT * FROM tbl_emp e <where> <choose> <when test="empId !=null"> e.emp_id = #{emp_id, jdbcType=INTEGER} </when> <when test="empName != null and empName != ''"> AND e.emp_name LIKE CONCAT(CONCAT('%', #{emp_name, jdbcType=VARCHAR}),'%') </when> <when test="email != null "> AND e.email = #{email, jdbcType=VARCHAR} </when> <otherwise> </otherwise> </choose> </where> </select> -
trim (where, set)——需要去掉 where、and、逗號(hào)之類的符號(hào)的時(shí)候。 注意最后一個(gè)條件
<update id="updateByPrimaryKeySelective" parameterType="com.javacoo.crud.bean.Employee"> update tbl_em <set> <if test="empName != null"> emp_name = #{empName,jdbcType=VARCHAR}, </if> <if test="gender != null"> gender = #{gender,jdbcType=CHAR}, </if> <if test="email != null"> email = #{email,jdbcType=VARCHAR}, </if> <if test="dId != null"> d_id = #{dId,jdbcType=INTEGER}, </if> </set> where emp_id = #{empId,jdbcType=INTEGER} </update>trim 用來指定或者去掉前綴或者后綴:
<insert id="insertSelective" parameterType="com.javacoo.crud.bean.Employee"> insert into tbl_emp <trim prefix="(" suffix=")" suffixOverrides=","> <if test="empId != null"> emp_id, </if> <if test="empName != null"> emp_name, </if> <if test="dId != null"> d_id, </if> </trim> <trim prefix="values (" suffix=")" suffixOverrides=","> <if test="empId != null"> #{empId,jdbcType=INTEGER}, </if> <if test="empName != null"> #{empName,jdbcType=VARCHAR}, </if> <if test="dId != null"> #{dId,jdbcType=INTEGER}, </if> </trim> </insert> -
foreach —— 需要遍歷集合的時(shí)候:
<delete id="deleteByList" parameterType="java.util.List"> delete from tbl_emp where emp_id in <foreach collection="list" item="item" open="(" separator="," close=")"> #{item.empId,jdbcType=VARCHAR} </foreach> </delete>動(dòng)態(tài) SQL 主要是用來解決 SQL 語句生成的問
批量操作
我們?cè)谏a(chǎn)的項(xiàng)目中會(huì)有一些批量操作的場(chǎng)景,比如導(dǎo)入文件批量處理數(shù)據(jù)的情況 (批量新增商戶、批量修改商戶信息),當(dāng)數(shù)據(jù)量非常大,比如超過幾萬條的時(shí)候,在 Java 代碼中循環(huán)發(fā)送 SQL 到數(shù)據(jù)庫執(zhí)行肯定是不現(xiàn)實(shí)的,因?yàn)檫@個(gè)意味著要跟數(shù)據(jù)庫創(chuàng) 建幾萬次會(huì)話,即使我們使用了數(shù)據(jù)庫連接池技術(shù),對(duì)于數(shù)據(jù)庫服務(wù)器來說也是不堪重 負(fù)的。
在 MyBatis 里面是支持批量的操作的,包括批量的插入、更新、刪除。我們可以直 接傳入一個(gè) List、Set、Map 或者數(shù)組,配合動(dòng)態(tài) SQL 的標(biāo)簽,MyBatis 會(huì)自動(dòng)幫我們 生成語法正確的 SQL 語句。
比如我們來看兩個(gè)例子,批量插入和批量更新。
-
批量插入
批量插入的語法是這樣的,只要在 values 后面增加插入的值就可以了。
insert into tbl_emp (emp_id, emp_name, gender,email, d_id) values ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) , ( ?,?,?,?,? ) ,在 Mapper 文件里面,我們使用 foreach 標(biāo)簽拼接 values 部分的語句:
<!-- 批量插入 --> <insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true"> <selectKey resultType="long" keyProperty="id" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> insert into tbl_emp (emp_id, emp_name, gender,email, d_id) values <foreach collection="list" item="emps" index="index" separator=","> ( #{emps.empId},#{emps.empName},#{emps.gender},#{emps.email},#{emps.dId} ) </foreach> </insert>Java 代碼里面,直接傳入一個(gè) List 類型的參數(shù).
我們來測(cè)試一下。效率要比循環(huán)發(fā)送 SQL 執(zhí)行要高得多。最關(guān)鍵的地方就在于減少了跟數(shù)據(jù)庫交互的次數(shù),并且避免了開啟和結(jié)束事務(wù)的時(shí)間消耗。
-
批量更新
批量更新的語法是這樣的,通過 case when,來匹配 id 相關(guān)的字段值。
update tbl_emp set emp_name = case emp_id when ? then ? when ? then ? when ? then ? end , gender = case emp_id when ? then ? when ? then ? when ? then ? end , email = case emp_id when ? then ? when ? then ? when ? then ? end where emp_id in ( ? , ? , ? )所以在 Mapper 文件里面最關(guān)鍵的就是 case when 和 where 的配置.需要注意一下 open 屬性和 separator屬性。
<update id="updateBatch"> update tbl_emp set emp_name = <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end"> when #{emps.empId} then #{emps.empName} </foreach> ,gender = <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end"> when #{emps.empId} then #{emps.gender} </foreach> ,email = <foreach collection="list" item="emps" index="index" separator=" " open="case emp_id" close="end"> when #{emps.empId} then #{emps.email} </foreach> where emp_id in <foreach collection="list" item="emps" index="index" separator="," open="(" close=")"> #{emps.empId} </foreach> </update>批量刪除也是類似的。
Batch Executor
當(dāng)然 MyBatis 的動(dòng)態(tài)標(biāo)簽的批量操作也是存在一定的缺點(diǎn)的,比如數(shù)據(jù)量特別大的 時(shí)候,拼接出來的 SQL 語句過大。 MySQL 的服務(wù)端對(duì)于接收的數(shù)據(jù)包有大小限制,max_allowed_packet 默認(rèn)是 4M,需要修改默認(rèn)配置才可以解決這個(gè)問題。
Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (7188967 >
4194304). You can change this value on the server by setting the max_allowed_packet' variable.
在我們的全局配置文件中,可以配置默認(rèn)的 Executor 的類型。其中有一種 BatchExecutor。
<setting name="defaultExecutorType" value="BATCH" />
也可以在創(chuàng)建會(huì)話的時(shí)候指定執(zhí)行器類型:
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
BatchExecutor 底層是對(duì) JDBC ps.addBatch()的封裝,原理是攢一批 SQL 以后再發(fā)送如:
@Test
public void testJdbcBatch() throws IOException {
Connection conn = null;
PreparedStatement ps = null;
try {
// 注冊(cè) JDBC 驅(qū)動(dòng)
Class.forName("com.mysql.jdbc.Driver");
// 打開連接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jc-mybatis?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true", "root", "123456");
ps = conn.prepareStatement(
"INSERT into blog values (?, ?, ?)");
for (int i = 1000; i < 101000; i++) {
Blog blog = new Blog();
ps.setInt(1, i);
ps.setString(2, String.valueOf(i));
ps.setInt(3, 1001);
ps.addBatch();
}
ps.executeBatch();
// conn.commit();
ps.close();
conn.close();
} catch (SQLException se) {
se.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (ps != null) ps.close();
} catch (SQLException se2) {
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
問題:三種執(zhí)行器的區(qū)別是什么?Simple、Reuse、Batch
嵌套(關(guān)聯(lián))查詢/ N+1 / 延遲加載
我們?cè)诓樵儤I(yè)務(wù)數(shù)據(jù)的時(shí)候經(jīng)常會(huì)遇到跨表關(guān)聯(lián)查詢的情況,比如查詢員工就會(huì)關(guān) 聯(lián)部門(一對(duì)一),查詢成績(jī)就會(huì)關(guān)聯(lián)課程(一對(duì)一),查詢訂單就會(huì)關(guān)聯(lián)商品(一對(duì)多),等等。

我們映射結(jié)果有兩個(gè)標(biāo)簽,一個(gè)是 resultType,一個(gè)是 resultMap。
resultType 是 select 標(biāo)簽的一個(gè)屬性,適用于返回 JDK 類型(比如 Integer、String 等等)和實(shí)體類。這種情況下結(jié)果集的列和實(shí)體類的屬性可以直接映射。如果返回的字段無法直接映射,就要用 resultMap 來建立映射關(guān)系。
對(duì)于關(guān)聯(lián)查詢的這種情況,通常不能用 resultType 來映射。用 resultMap 映射,要 么就是修改 dto(Data Transfer Object),在里面增加字段,這個(gè)會(huì)導(dǎo)致增加很多無關(guān)的字段。要么就是引用關(guān)聯(lián)的對(duì)象,比如 Blog 里面包含了一個(gè) Author 對(duì)象,這種情況下就要用到關(guān)聯(lián)查詢(association,或者嵌套查詢),MyBatis 可以幫我們自動(dòng)做結(jié)果的映射。
一對(duì)一的關(guān)聯(lián)查詢有兩種配置方式:
-
嵌套結(jié)果:
/** * 一對(duì)一,一篇文章對(duì)應(yīng)一個(gè)作者 * 嵌套結(jié)果,不存在N+1問題 */ @Test public void testSelectBlogWithAuthorResult() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); BlogAndAuthor blog = mapper.selectBlogWithAuthorResult(1); System.out.println("-----------:"+blog); }xml
<!-- 根據(jù)文章查詢作者,一對(duì)一查詢的結(jié)果,嵌套查詢 --> <resultMap id="BlogWithAuthorResultMap" type="com.javacoo.domain.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <!-- 聯(lián)合查詢,將 author 的屬性映射到 ResultMap --> <association property="author" javaType="com.javacoo.domain.Author"> <id column="author_id" property="authorId"/> <result column="author_name" property="authorName"/> </association> </resultMap> -
嵌套查詢
/** * 一對(duì)一,一篇文章對(duì)應(yīng)一個(gè)作者 * 嵌套查詢,會(huì)有N+1的問題 */ @Test public void testSelectBlogWithAuthorQuery() throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); BlogMapper mapper = session.getMapper(BlogMapper.class); BlogAndAuthor blog = mapper.selectBlogWithAuthorQuery(1); System.out.println("-----------:"+blog.getClass()); // 如果開啟了延遲加載,會(huì)在使用的時(shí)候才發(fā)出SQL // equals,clone,hashCode,toString也會(huì)觸發(fā)延遲加載 // System.out.println("-----------調(diào)用toString方法:"+blog); System.out.println("-----------getAuthor:"+blog.getAuthor().toString()); // 如果 aggressiveLazyLoading = true ,也會(huì)觸發(fā)加載,否則不會(huì) //System.out.println("-----------getName:"+blog.getName()); }xml
<!-- 另一種聯(lián)合查詢 (一對(duì)一)的實(shí)現(xiàn),但是這種方式有“N+1”的問題 --> <resultMap id="BlogWithAuthorQueryMap" type="com.javacoo.domain.associate.BlogAndAuthor"> <id column="bid" property="bid" jdbcType="INTEGER"/> <result column="name" property="name" jdbcType="VARCHAR"/> <association property="author" javaType="com.javacoo.domain.Author" column="author_id" select="selectAuthor"/> <!-- selectAuthor 定義在下面--> </resultMap> <!-- 嵌套查詢 --> <select id="selectAuthor" parameterType="int" resultType="com.javacoo.domain.Author"> select author_id authorId, author_name authorName from author where author_id = #{authorId} </select>其中第二種方式:嵌套查詢,由于是分兩次查詢,當(dāng)我們查詢了員工信息之后,會(huì)再發(fā)送一條 SQL 到數(shù)據(jù)庫查詢部門信息。
我們只執(zhí)行了一次查詢員工信息的 SQL(所謂的 1),如果返回了 N 條記錄,就會(huì) 再發(fā)送 N 條到數(shù)據(jù)庫查詢部門信息(所謂的 N),這個(gè)就是我們所說的 N+1 的問題。 這樣會(huì)白白地浪費(fèi)我們的應(yīng)用和數(shù)據(jù)庫的性能。
如果我們用了嵌套查詢的方式,怎么解決這個(gè)問題?能不能等到使用部門信息的時(shí)候再去查詢?這個(gè)就是我們所說的延遲加載,或者叫懶加載。
在 MyBatis 里面可以通過開啟延遲加載的開關(guān)來解決這個(gè)問題。
在 settings 標(biāo)簽里面可以配置
<!--延遲加載的全局開關(guān)。當(dāng)開啟時(shí),所有關(guān)聯(lián)對(duì)象都會(huì)延遲加載。默認(rèn) false --> <setting name="lazyLoadingEnabled" value="true"/> <!--當(dāng)開啟時(shí),任何方法的調(diào)用都會(huì)加載該對(duì)象的所有屬性。默認(rèn) false,可通過 select 標(biāo)簽的fetchType 來覆蓋--> <setting name="aggressiveLazyLoading" value="false"/> <!-- Mybatis 創(chuàng)建具有延遲加載能力的對(duì)象所用到的代理工具,默認(rèn) JAVASSIST --> <setting name="proxyFactory" value="CGLIB" />lazyLoadingEnabled 決定了是否延遲加載。
aggressiveLazyLoading 決定了是不是對(duì)象的所有方法都會(huì)觸發(fā)查詢。
先來測(cè)試一下(也可以改成查詢列表)
1、沒有開啟延遲加載的開關(guān),會(huì)連續(xù)發(fā)送兩次查詢;
2 、 開 啟 了 延 遲 加 載 的 開 關(guān) , 調(diào) 用 blog.getAuthor() 以 及 默 認(rèn) 的 (equals,clone,hashCode,toString)時(shí)才會(huì)發(fā)起第二次查詢,其他方法并不會(huì)觸發(fā)查詢,比如 blog.getName();
3、如果開啟了 aggressiveLazyLoading=true,其他方法也會(huì)觸發(fā)查詢,比如 blog.getName()。
問題:為什么可以做到延遲加載?blog.getAuthor(),只是一個(gè)獲取屬性的方法, 里面并沒有連接數(shù)據(jù)庫的代碼,為什么會(huì)觸發(fā)對(duì)數(shù)據(jù)庫的查詢呢? 我懷疑:blog 根本不是 Blog 對(duì)象,而是被人動(dòng)過了手腳! 把這個(gè)對(duì)象打印出來看看:
System.out.println(blog.getClass());果然不對(duì):
class com.javacoo.domain.associate.BlogAndAuthor_$$_jvst70_0這個(gè)類的名字后面有 jvst,是 JAVASSIST 的縮寫。原來到這里帶延遲加載功能的對(duì)象 blog 已經(jīng)變成了一個(gè)代理對(duì)象,那到底什么時(shí)候變成代理對(duì)象的?我們后面在看源碼的時(shí)候再去分析。
【問題】當(dāng)開啟了延遲加載的開關(guān),對(duì)象是怎么變成代理對(duì)象的?
DefaultResultSetHandler.createResultObject()
既然是代理對(duì)象,那么必須要有一種創(chuàng)建代理對(duì)象的方法。我們有哪些實(shí)現(xiàn)動(dòng)態(tài)代理的方式?
這個(gè)就是為什么 settings 里面提供了一個(gè) ProxyFactory 屬性。MyBatis 默認(rèn)使用 JAVASSIST 創(chuàng)建代理對(duì)象。也可以改為 CGLIB,這時(shí)需要引入 CGLIB 的包。
【問題】CGLIB 和 JAVASSIST 區(qū)別是什么?
測(cè)試一下,我們把默認(rèn)的 JAVASSIST 修改為 CGLIB,再打印這個(gè)對(duì)象。
【問題】
1、resultType 和 resultMap 的區(qū)別?
2、collection 的區(qū)別?
MBG 與 Example
https://github.com/mybatis/generator
我們?cè)陧?xiàng)目中使用 MyBaits 的時(shí)候,針對(duì)需要操作的一張表,需要?jiǎng)?chuàng)建實(shí)體類、 Mapper 映射器、Mapper 接口,里面又有很多的字段和方法的配置,這部分的工作是非常繁瑣的。而大部分時(shí)候我們對(duì)于表的操作是相同的,比如根據(jù)主鍵查詢、根據(jù) Map 查詢、單條插入、批量插入、根據(jù)主鍵刪除等等等等。當(dāng)我們的表很多的時(shí)候,意味著有大量的重復(fù)工作。所以有沒有一種辦法,可以根據(jù)我們的表,自動(dòng)生成實(shí)體類、Mapper 映射器、Mapper 接口,里面包含了我們需要用到的這些基本方法和 SQL 呢?
MyBatis 提供了一個(gè)這樣的東西,叫做 MyBatis Generator,簡(jiǎn)稱 MBG。我們只 需要修改一個(gè)配置文件,使用相關(guān)的 jar 包命令或者 Java 代碼就可以幫助我們生成實(shí)體 類、映射器和接口文件。不知道用 MyBatis 的同學(xué)有沒有跟當(dāng)年的我一樣,還是實(shí)體類 的一個(gè)一個(gè)字段,接口的一個(gè)一個(gè)方法,映射器的一條一條 SQL 去寫的。
MBG 的配置文件里面有一個(gè) Example 的開關(guān),這個(gè)東西用來構(gòu)造復(fù)雜的篩選條件 的,換句話說就是根據(jù)我們的代碼去生成 where 條件.
原理:在實(shí)體類中包含了兩個(gè)有繼承關(guān)系的 Criteria,用其中自動(dòng)生成的方法來構(gòu)建 查詢條件。把這個(gè)包含了 Criteria 的實(shí)體類作為參數(shù)傳到查詢參數(shù)中,在解析 Mapper 映射器的時(shí)候會(huì)轉(zhuǎn)換成 SQL 條件。
實(shí)例:查詢 bid=1 的 Blog,通過創(chuàng)建一個(gè) Criteria 去構(gòu)建查詢條件:
BlogMapper mapper = session.getMapper(BlogMapper.class);
BlogExample example = new BlogExample();
BlogExample.Criteria criteria = example.createCriteria();
criteria.andBidEqualTo(1);
List<Blog> list = mapper.selectByExample(example);
生成的語句:
select 'true' as QUERYID, bid, name, author_id from blog WHERE ( bid = ?
翻頁
在寫存儲(chǔ)過程的年代,翻頁也是一件很難調(diào)試的事情,我們要實(shí)現(xiàn)數(shù)據(jù)不多不少準(zhǔn) 確地返回,需要大量的調(diào)試和修改。但是如果自己手寫過分頁,就能清楚分頁的原理。
邏輯翻頁與物理翻頁
在我們查詢數(shù)據(jù)庫的操作中,有兩種翻頁方式,一種是邏輯翻頁(假分頁),一種是物理翻頁(真分頁)。邏輯翻頁的原理是把所有數(shù)據(jù)查出來,在內(nèi)存中刪選數(shù)據(jù)。 物理翻頁是真正的翻頁,比如 MySQL 使用 limit 語句,Oracle 使用 rownum 語句,SQL Server 使用 top 語句。
邏輯翻頁
MyBatis 里面有一個(gè)邏輯分頁對(duì)象 RowBounds,里面主要有兩個(gè)屬性,offset 和 limit(從第幾條開始,查詢多少條)。
我們可以在 Mapper 接口的方法上加上這個(gè)參數(shù),不需要修改 xml 里面的 SQL 語句。
/**
* 邏輯分頁
* @throws IOException
*/
@Test
public void testSelectByRowBounds() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
int start = 0; // offset
int pageSize = 5; // limit
RowBounds rb = new RowBounds(start, pageSize);
List<Blog> list = mapper.selectBlogList(rb); // 使用邏輯分頁
for(Blog b :list){
System.out.println(b);
}
} finally {
session.close();
}
}
它的底層其實(shí)是對(duì) ResultSet 的處理。它會(huì)舍棄掉前面 offset 條數(shù)據(jù),然后再取剩 下的數(shù)據(jù)的 limit 條。
// DefaultResultSetHandler.java
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws
SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext();
ResultSet resultSet = rsw.getResultSet();
this.skipRows(resultSet, rowBounds);
while(this.shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() &&
resultSet.next()) {
ResultMap discriminatedResultMap = this.resolveDiscriminatedResultMap(resultSet,
resultMap, (String)null);
Object rowValue = this.getRowValue(rsw, discriminatedResultMap, (String)null);
this.storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
很明顯,如果數(shù)據(jù)量大的話,這種翻頁方式效率會(huì)很低(跟查詢到內(nèi)存中再使用 subList(start,end)沒什么區(qū)別)。所以我們要用到物理翻頁。
物理翻頁
物理翻頁是真正的翻頁,它是通過數(shù)據(jù)庫支持的語句來翻頁。
第一種簡(jiǎn)單的辦法就是傳入?yún)?shù)(或者包裝一個(gè) page 對(duì)象),在 SQL 語句中翻頁。
<select id="selectBlogPage" parameterType="map" resultMap="BaseResultMap">
select * from blog limit #{curIndex} , #{pageSize}
</select>
第一個(gè)問題是我們要在 Java 代碼里面去計(jì)算起止序號(hào);第二個(gè)問題是:每個(gè)需要翻 頁的 Statement 都要編寫 limit 語句,會(huì)造成 Mapper 映射器里面很多代碼冗
那我們就需要一種通用的方式,不需要去修改配置的任何一條 SQL 語句,只要在我 們需要翻頁的地方封裝一下翻頁對(duì)象就可以了。 我們最常用的做法就是使用翻頁的插件,這個(gè)是基于 MyBatis 的攔截器實(shí)現(xiàn)的,比如 PageHelper。
// pageSize 每一頁幾條
PageHelper.startPage(pn, 10);
List<Employee> emps = employeeService.getAll();
// navigatePages 導(dǎo)航頁碼數(shù)
PageInfo page = new PageInfo(emps, 10);
return Msg.success().add("pageInfo", page);
PageHelper 是通過 MyBatis 的攔截器實(shí)現(xiàn)的,插件的具體原理我們后面的課再分析。簡(jiǎn)單地來說,它會(huì)根據(jù) PageHelper 的參數(shù),改寫我們的 SQL 語句。比如 MySQL 會(huì)生成 limit 語句,Oracle 會(huì)生成 rownum 語句,SQL Server 會(huì)生成 top 語
通用 Mapper
問題:當(dāng)我們的表字段發(fā)生變化的時(shí)候,我們需要修改實(shí)體類和 Mapper 文件定義的字段和方法。如果是增量維護(hù),那么一個(gè)個(gè)文件去修改。如果是全量替換,我們還要去對(duì)比用 MBG 生成的文件。字段變動(dòng)一次就要修改一次,維護(hù)起來非常麻煩。 解決這個(gè)問題,我們有兩種思路。 第 一 個(gè) , 因?yàn)镸yBatis的 Mapper 是 支 持 繼 承 的 ( 見:https://github.com/mybatis/mybatis-3/issues/35 ) 。 所以我們可以把我們的Mapper.xml 和 Mapper 接口都分成兩個(gè)文件。一個(gè)是 MBG 生成的,這部分是固定不變的。然后創(chuàng)建 DAO 類繼承生成的接口,變化的部分就在 DAO 里面維護(hù)。
public interface BlogMapperExt extends BlogMapper {
public Blog selectBlogByName(String name);
}
<?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.javacoo.mapper.BlogMapperExt">
<!-- 只能繼承 statement,不能繼承 sql、resultMap 等標(biāo)簽 -->
<resultMap id="BaseResultMap" type="com.javacoo.domain.Blog">
<id column="bid" property="bid" jdbcType="INTEGER"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="author_id" property="authorId" jdbcType="INTEGER"/>
</resultMap>
<!-- 在 parent xml 和 child xml 的 statement id 相同的情況下,會(huì)使用 child xml 的 statement
id -->
<select id="selectBlogByName" resultMap="BaseResultMap" statementType="PREPARED">
select * from blog where name = #{name}
</select>
</mapper>
mybatis-config.xml 里面也要掃描:
<mappers>
<mapper resource="BlogMapper.xml"/>
<mapper resource="BlogMapperExt.xml"/>
</mappers>
所以以后只要修改 Ext 的文件就可以了。這么做有一個(gè)缺點(diǎn),就是文件會(huì)增多。
思考:既然針對(duì)每張表生成的基本方法都是一樣的,也就是公共的方法部分代碼都是一樣的,我們能不能把這部分合并成一個(gè)文件,讓它支持泛型呢?
太聰明了,當(dāng)然可以!
編寫一個(gè)支持泛型的通用接口,比如叫 JCBaseMapper,把實(shí)體類作為參數(shù)傳 入。這個(gè)接口里面定義了大量的增刪改查的基礎(chǔ)方法,這些方法都是支持泛型的。 自 定 義 的 Mapper 接 口 繼 承 該 通 用 接 口 , 例 如 BlogMapper extends GPBaseMapper,自動(dòng)獲得對(duì)實(shí)體類的操作方法。遇到?jīng)]有的方法,我們依然 可以在我們自己的 Mapper 里面編寫。
我們能想到的解決方案,早就有人做了這個(gè)事了,這個(gè)東西就叫做通用 Mapper。
https://github.com/abel533/Mapper/wiki
用途:主要解決單表的增刪改查問題,并不適用于多表關(guān)聯(lián)查詢的場(chǎng)景。
除了配置文件變動(dòng)的問題之外,通用 Mapper 還可以解決:
1、 每個(gè) Mapper 接口中大量的重復(fù)方法的定義;
2、 屏蔽數(shù)據(jù)庫的差異;
3、 提供批量操作的方法;
4、 實(shí)現(xiàn)分頁。
通用 Mapper 和 PageHelper 作者是同一個(gè)人(劉增輝)。
使用方式:在 Spring 中使用時(shí),引入 jar 包,替換 applicationContext.xml 中的 sqlSessionFactory 和 configure。
<bean class="tk.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.javacoo.crud.dao"/>
</bean>
MyBatis-Plus
MyBatis-Plus 是原生 MyBatis 的一個(gè)增強(qiáng)工具,可以在使用原生 MyBatis 的所有功能的基礎(chǔ)上,使用 plus 特有的功能。
MyBatis-Plus 的核心功能:
- 通用 CRUD:定義好 Mapper 接口后,只需要繼承 BaseMapper 接口即 可獲得通用的增刪改查功能,無需編寫任何接口方法與配置文件。
- 條件構(gòu)造器:通過 EntityWrapper(實(shí)體包裝類),可以用于拼接 SQL語句,并且支持排序、分組查詢等復(fù)雜的 SQL。
- 代碼生成器:支持一系列的策略配置與全局配置,比 MyBatis 的代碼生成更好用。 另外MyBatis-Plus也有分頁的功能。
一些信息
路漫漫其修遠(yuǎn)兮,吾將上下而求索
碼云:https://gitee.com/javacoo
QQ群:164863067
作者/微信:javacoo
郵箱:xihuady@126.com