MyBatis持久層框架
目錄
-
一、Mybatis簡介
-
1. 簡介
-
2. 持久層框架對比
-
3. 快速入門(基于Mybatis3方式)
-
-
二、日志框架擴展
-
1. 用日志打印替代sout
-
2. Java日志體系演變
-
3. 最佳拍檔用法
-
4. Lombok插件的使用
-
4.1 Lombok簡介
-
4.2 Lombok安裝
-
4.3 Lombok使用注解
-
-
-
三、MyBatis基本使用
-
1. 向SQL語句傳參
-
1.1 mybatis日志輸出配置
-
1.2 #{}形式
-
1.3 ${}形式
-
-
2. 數據輸入
-
2.1 Mybatis總體機制概括
-
2.2 概念說明
-
2.3 單個簡單類型參數
-
2.4 實體類類型參數
-
2.5 零散的簡單類型數據
-
2.6 Map類型參數
-
-
3. 數據輸出
-
3.1 輸出概述
-
3.2 單個簡單類型
-
3.3 返回實體類對象
-
3.4 返回Map類型
-
3.5 返回List類型
-
3.6 返回主鍵值
-
3.7 實體類屬性和數據庫字段對應關系
-
-
4. CRUD強化練習
-
5. mapperXML標簽總結
-
-
四、MyBatis多表映射
-
1. 多表映射概念
-
2. 對一映射
-
3. 對多映射
-
4. 多對多映射
-
5. 多表映射總結
-
5.1 多表映射優化
-
5.2 多表映射總結
-
-
-
五、MyBatis動態語句
-
1. 動態語句需求和簡介
-
2. if和where標簽
-
3. set標簽
-
4. trim標簽(了解)
-
5. choose/when/otherwise標簽
-
6. foreach標簽
-
7. sql片段
-
-
六、MyBatis高級擴展
-
1. Mapper批量映射優化
-
2. 插件和分頁插件PageHelper
-
2.1 插件機制和PageHelper插件介紹
-
2.2 PageHelper插件使用
-
-
3. 逆向工程和MybatisX插件
-
3.1 ORM思維介紹
-
3.1 逆向工程
-
3.2 逆向工程插件MyBatisX使用
-
-
-
七、MyBatis總結
一、Mybatis簡介
1. 簡介
https://mybatis.org/mybatis-3/zh/index.html
MyBatis最初是Apache的一個開源項目iBatis, 2010年6月這個項目由Apache Software Foundation遷移到了Google Code。隨著開發團隊轉投Google Code旗下, iBatis3.x正式更名為MyBatis。代碼于2013年11月遷移到Github。
MyBatis 是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為數據庫中的記錄。
MyBatis是持久層的一個半自動化的ORM框架 ORM:Object RelationalShip Mapping 對象關系映射 BaseDao: executeUpdate(),executeQuery()->解析結果集->通過反射技術再封裝成實體類
社區會持續更新開源項目,版本會不斷變化,我們不必每個小版本都追,關注重大更新的大版本升級即可。
本課程使用:3.5.11版本
2. 持久層框架對比
-
JDBC
-
SQL 夾雜在Java代碼中耦合度高,導致硬編碼內傷
-
維護不易且實際開發需求中 SQL 有變化,頻繁修改的情況多見
-
代碼冗長,開發效率低
-
-
Hibernate 和 JPA SSH:Struts spring Hibernate
-
操作簡便,開發效率高
-
程序中的長難復雜 SQL 需要繞過框架
-
內部自動生成的 SQL,不容易做特殊優化
-
基于全映射的全自動框架,大量字段的 POJO 進行部分映射時比較困難。
-
反射操作太多,導致數據庫性能下降
-
-
MyBatis
-
輕量級,性能出色
-
SQL 和 Java 編碼分開,功能邊界清晰。Java代碼專注業務、SQL語句專注數據
-
開發效率稍遜于 Hibernate,但是完全能夠接收
-
開發效率:Hibernate>Mybatis>JDBC
運行效率:JDBC>Mybatis>Hibernate
封裝程度越高,開發效率就會越高,但是性能就會越差
3. 快速入門(基于Mybatis3方式)
-
準備數據模型
CREATE DATABASE `mybatis-example`;USE `mybatis-example`;CREATE TABLE `t_emp`(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66); INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);
-
項目搭建和準備
-
項目搭建
-
依賴導入
pom.xml
<dependencies><!-- mybatis依賴 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><!-- MySQL驅動 mybatis底層依賴jdbc驅動實現,本次不需要導入連接池,mybatis自帶! --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><!--junit5測試--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency> </dependencies>
-
實體類準備
package com.cuihub.mybatis.pojo;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;@Data @NoArgsConstructor @AllArgsConstructor public class Person {private Integer pid;private String pname,address; }
-
-
準備Mapper接口和MapperXML文件
MyBatis 框架下,SQL語句編寫位置發生改變,從原來的Java類,改成XML或者注解定義!
推薦在XML文件中編寫SQL語句,讓用戶能更專注于 SQL 代碼,不用關注其他的JDBC代碼。
如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼!!
一般編寫SQL語句的文件命名:XxxMapper.xml Xxx一般取表名!!
Mybatis 中的 Mapper 接口相當于以前的 Dao。但是區別在于,Mapper 僅僅只是建接口即可,我們不需要提供實現類,具體的SQL寫到對應的Mapper文件,該用法的思路如下圖所示:
-
定義mapper接口
包:com.atguigu.mapper
package com.atguigu.mapper;import com.atguigu.pojo.Employee;/*** t_emp表對應數據庫SQL語句映射接口!* 接口只規定方法,參數和返回值!* mapper.xml中編寫具體SQL語句!*/ public interface EmployeeMapper {/*** 根據員工id查詢員工數據方法* @param empId 員工id* @return 員工實體對象*/Employee selectEmployee(Integer empId);}
-
定義mapper xml
位置: resources/mappers/EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace等于mapper接口類的全限定名,這樣實現對應 --> <mapper namespace="com.atguigu.mapper.EmployeeMapper"><!-- 查詢使用 select標簽id = 方法名resultType = 返回值類型標簽內編寫SQL語句--><select id="selectEmployee" resultType="com.atguigu.pojo.Employee"><!-- #{empId}代表動態傳入的參數,并且進行賦值!后面詳細講解 -->select emp_id empId,emp_name empName, emp_salary empSalary from t_emp where emp_id = #{empId}</select> </mapper>
注意:
-
方法名和SQL的id一致
-
方法返回值和resultType一致
-
方法的參數和SQL的參數一致
-
接口的全類名和映射配置文件的名稱空間一致
-
-
-
準備MyBatis配置文件
mybatis框架配置文件: 數據庫連接信息,性能配置,mapper.xml配置等!
習慣上命名為 mybatis-config.xml,這個文件名僅僅只是建議,并非強制要求。將來整合 Spring 之后,這個配置文件可以省略,所以大家操作時可以直接復制、粘貼。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><!-- environments表示配置Mybatis的開發環境,可以配置多個環境,在眾多具體環境中,使用default屬性指定實際運行時使用的環境。default屬性的取值是environment標簽的id屬性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一個具體的環境 --><environment id="development"><!-- Mybatis的內置的事務管理器 --><transactionManager type="JDBC"/><!-- 配置數據源 --><dataSource type="POOLED"><!-- 建立數據庫連接的具體信息 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><!-- Mapper注冊:指定Mybatis映射文件的具體位置 --><!-- mapper標簽:配置一個具體的Mapper映射文件 --><!-- resource屬性:指定Mapper映射文件的實際存儲位置,這里需要使用一個以類路徑根目錄為基準的相對路徑 --><!-- 對Maven工程的目錄結構來說,resources目錄下的內容會直接放入類路徑,所以這里我們可以以resources目錄為基準 --><mapper resource="mappers/EmployeeMapper.xml"/></mappers></configuration>
-
運行和測試
/*** projectName: com.atguigu.test** description: 測試類*/ public class MyBatisTest {@Testpublic void testSelectEmployee() throws IOException {// 1.創建SqlSessionFactory對象// ①聲明Mybatis全局配置文件的路徑String mybatisConfigFilePath = "mybatis-config.xml";// ②以輸入流的形式加載Mybatis配置文件InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);// ③基于讀取Mybatis配置文件的輸入流創建SqlSessionFactory對象SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 2.使用SqlSessionFactory對象開啟一個會話SqlSession session = sessionFactory.openSession();// 3.根據EmployeeMapper接口的Class對象獲取Mapper接口類型的對象(動態代理技術)EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);// 4. 調用代理類方法既可以觸發對應的SQL語句Employee employee = employeeMapper.selectEmployee(1);System.out.println("employee = " + employee);// 4.關閉SqlSessionsession.commit(); //提交事務 [DQL不需要,其他需要]session.close(); //關閉會話} }
說明:
-
SqlSession:代表Java程序和數據庫之間的會話。(HttpSession是Java程序和瀏覽器之間的會話)
-
SqlSessionFactory:是“生產”SqlSession的“工廠”。
-
工廠模式:如果創建某一個對象,使用的過程基本固定,那么我們就可以把創建這個對象的相關代碼封裝到一個“工廠類”中,以后都使用這個工廠類來“生產”我們需要的對象。
-
-
SqlSession和HttpSession區別
-
HttpSession:工作在Web服務器上,屬于表述層。
-
代表瀏覽器和Web服務器之間的會話。
-
-
SqlSession:不依賴Web服務器,屬于持久化層。
-
代表Java程序和數據庫之間的會話。?
-
-
二、日志框架擴展
1. 用日志打印替代sout
-
sout有什么問題
-
問題1:I/O影響性能
System.out對象是一個輸出流對象,所以控制臺輸出信息本質上是I/O操作。而I/O操作是項目運行過程中兩大性能瓶頸之一。
-
問題2:無法統一管理
項目上線時,希望把所有(或一部分)sout打印關閉,但是只能手動一個一個查找,耗費開發人員的極大精力,因為sout的無度使用會使它分散在項目的各個角落。
-
問題3:顯得你很low
想看某個變量的值,只會使用sout在控制臺打印出來,不會debug,這只能被人鄙視。
-
-
使用[日志框架]的好處
-
設定級別,統一管理
日志框架會按照事件的嚴重程度來劃分級別,例如:
-
錯誤(Error):表示程序運行出錯,比如拋異常等情況。
-
警告(Warning):表示程序運行過程中有潛在風險,但此時并沒有報錯。
-
信息(Info):表示程序運行過程中完成了一個關鍵動作,需要以程序運行信息的形式告知開發者。
-
調試(Debug):表示程序運行過程中更加細致的信息,協助程序員調試程序。
📌Tips:各種不同的具體日志系統會使用不同的日志級別名稱,也可能有更多個級別設定。但是思想是一致的。?通過在配置文件中指定某一個日志級別來控制系統要打印的內容。日志框架會打印當前指定級別的日志和比當前指定級別更嚴重的級別的日志。?例如在開發階段,我們指定debug級別,項目上線修改成info級別,那么所有debug級別的日志就都不打印了,不需要到項目代碼中一個一個修改,非常方便。
-
-
靈活指定輸出位置
使用日志框架不一定是打印到控制臺,也可以保存到文件中或者保存到數據庫。這就看具體的項目維護需求。
-
自定義日志格式
打印日志數據可以使用日志框架的默認格式,也可以根據需要定制。
-
基于日志分析問題
將來我們開發的應用系統中,不僅包含Java代碼,還有很多中間件服務器。任何子系統出現故障我們都是通過日志來定位問題、分析故障原因。甚至更復雜的系統還會專門開發日志子系統,在主系統出現問題時抓取日志數據供維護人員參考。
而日志數據必須要有確定格式才便于格式化和抓取,這肯定不是隨意寫sout就能實現的。
-
2. Java日志體系演變
1996年早期,歐洲安全電子市場項目組決定編寫它自己的程序跟蹤API(Tracing API)。經過不斷的完善,這個API終于成為一個十分受歡迎的Java日志軟件包,即Log4j(由Ceki創建)。后來Log4j成為Apache基金會項目中的一員,Ceki也加入Apache組織。? 后來Log4j近乎成了Java社區的日志標準。據說Apache基金會還曾經建議Sun引入Log4j到java的標準庫中,但Sun拒絕了。? 2002年Java1.4發布,Sun推出了自己的日志庫JUL(Java Util Logging),其實現基本模仿了Log4j的實現。在JUL出來以前,Log4j就已經成為一項成熟的技術,使得Log4j在選擇上占據了一定的優勢。? 接著,Apache推出了Jakarta Commons Logging門面,看來Apache想統一日志江湖了,JCL只是定義了一套日志接口(其內部也提供一個Simple Log的簡單實現),支持運行時動態加載日志組件的實現,也就是說,在你應用代碼里,只需調用Commons Logging的接口,底層實現可以是Log4j,也可以是JUL(Java實現)。? 后來(2006年),Ceki不適應Apache的工作方式,離開了Apache。然后先后創建了Slf4j(日志門面接口,類似于Commons Logging)和Logback(Slf4j的實現)兩個項目,并回瑞典創建了QOS公司,QOS官網上是這樣描述Logback的:The Generic,Reliable Fast&Flexible Logging Framework(一個通用,可靠,快速且靈活的日志框架)。
門面:類似于標準層、接口層
名稱 | 說明 |
---|---|
JCL(Jakarta Commons Logging) | 陳舊 |
SLF4J(Simple Logging Facade for Java) | 適合(同一作者) |
jboss-logging | 特殊專業領域使用 |
實現
名稱 | 說明 |
---|---|
log4j | 最初版(同一作者) |
JUL(java.util.logging) | JDK自帶 |
log4j2 | Apache收購log4j后全面重構,內部實現和log4j完全不同 |
logback | 優雅、強大(同一作者) |
最佳拍檔
-
門面:SLF4J
-
實現:logback
3. 最佳拍檔用法
-
依賴導入
<!-- 日志 , 會自動傳遞slf4j門面--> <dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version> </dependency>
-
代碼測試
代碼圖解
測試結果
-
引入配置
Logback要求配置文件名稱必須是logback.xml,存放路徑在main/resources目錄下。
通過配置文件,可以配置輸出格式、輸出級別、輸出位置等!
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"><!-- 指定日志輸出的位置,ConsoleAppender表示輸出到控制臺 --><appender name="STDOUT"class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 日志輸出的格式 --><!-- 按照順序分別是:時間、日志級別、線程名稱、打印日志的類、日志主體內容、換行 --><pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern><charset>UTF-8</charset></encoder></appender><!-- 設置全局日志級別。日志級別按順序分別是:TRACE、DEBUG、INFO、WARN、ERROR --><!-- 指定任何一個日志級別都只打印當前級別和后面級別的日志。 --><root level="DEBUG"><!-- 指定打印日志的appender,這里通過“STDOUT”引用了前面配置的appender --><appender-ref ref="STDOUT" /></root><!-- 根據特殊需求指定局部日志級別,可以是包名或全類名。 --><logger name="com.atguigu.mybatis" level="DEBUG" /></configuration>
-
mybatis日志輸出配置
我們可以在mybatis的配置文件使用settings標簽設置,輸出運行過程SQL日志!
日志配置:
<settings><!-- SLF4J 選擇slf4j輸出! --><setting name="logImpl" value="SLF4J"/> </settings>
4. Lombok插件的使用
4.1 Lombok簡介
傳統實體類
public class Employee {private Integer empId;private String empName;private Double empSalary;public Employee() {}public Integer getEmpId() {return empId;}public void setEmpId(Integer empId) {this.empId = empId;}public String getEmpName() {return empName;}public void setEmpName(String empName) {this.empName = empName;}public Double getEmpSalary() {return empSalary;}public void setEmpSalary(Double empSalary) {this.empSalary = empSalary;}@Overridepublic String toString() {return "Employee{" +"empId=" + empId +", empName='" + empName + '\'' +", empSalary=" + empSalary +'}';}public Employee(Integer empId, String empName, Double empSalary) {this.empId = empId;this.empName = empName;this.empSalary = empSalary;} }
使用Lombok
既然getXxx()、setXxx()方法、toString()方法、構造器都是按照固定格式生成的,那能否由程序自動生成呢?
使用Lombok注解就可以省略生成getXxx()、setXxx()方法、toString()方法、構造器等固定格式代碼的繁瑣操作,提高開發效率。包括Logger日志對象。
Lombok原理
Lombok是將自動生成的代碼織入字節碼文件中,從而實現:源代碼沒有,但是字節碼文件有——畢竟我們最終運行的是字節碼文件,只要字節碼文件中有即可。而這個過程因為要參與源文件編譯,所以需要安裝IDEA插件。
4.2 Lombok安裝
4.3 Lombok使用注解
加入依賴
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.12</version><scope>provided</scope> </dependency>
注解功能
注解 | 作用 |
@Data | 生成getXxx()方法、setXxx()方法、toString()、equals()、canEqual()、hashCode()方法 |
@AllArgsConstructor | 生成全參構造器 |
@NoArgsConstructor | 生成無參構造器 |
@Slf4j | 生成日志對象 |
@Getter | 生成getXxx()方法 |
@Setter | 生成setXxx()方法 |
@ToString | 生成toString()方法 |
三、MyBatis基本使用
1. 向SQL語句傳參
1.2 #{}形式
Mybatis會將SQL語句中的#{}轉換為問號占位符。
1.3 ${}形式
${}形式傳參,底層Mybatis做的是字符串拼接操作。
通常不會采用${}的方式傳值。一個特定的適用場景是:通過Java程序動態生成數據庫表,表名部分需要Java程序通過參數傳入;而JDBC對于表名部分是不能使用問號占位符的,此時只能使用
結論:實際開發中,能用#{}實現的,肯定不用${}。
特殊情況: 動態的不是值,是列名或者關鍵字,需要使用${}拼接
//注解方式傳入參數!! @Select("select * from user where ${column} = #{value}") User findByColumn(@Param("column") String column, @Param("value") String value);
2. 數據輸入
2.1 Mybatis總體機制概括
2.2 概念說明
這里數據輸入具體是指上層方法(例如Service方法)調用Mapper接口時,數據傳入的形式。
-
簡單類型:只包含一個值的數據類型
-
基本數據類型:int、byte、short、double、……
-
基本數據類型的包裝類型:Integer、Character、Double、……
-
字符串類型:String
-
-
復雜類型:包含多個值的數據類型
-
實體類類型:Employee、Department、……
-
集合類型:List、Set、Map、……
-
數組類型:int[]、String[]、……
-
復合類型:List<Employee>、實體類中包含集合……
-
2.3 單個簡單類型參數
Mapper接口中抽象方法的聲明
Employee selectEmployee(Integer empId);
SQL語句
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId} </select>
單個簡單類型參數,在#{}中可以隨意命名,但是沒有必要。通常還是使用和接口方法參數同名。
2.4 實體類類型參數
Mapper接口中抽象方法的聲明
int insertEmployee(Employee employee);
SQL語句
<insert id="insertEmployee">insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary}) </insert>
對應關系
結論
Mybatis會根據#{}中傳入的數據,加工成getXxx()方法,通過反射在實體類對象中調用這個方法,從而獲取到對應的數據。填充到#{}解析后的問號占位符這個位置。
2.5 零散的簡單類型數據
零散的多個簡單類型參數,如果沒有特殊處理,那么Mybatis無法識別自定義名稱:
Mapper接口中抽象方法的聲明
int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
SQL語句
<update id="updateEmployee">update t_emp set emp_salary=#{empSalary} where emp_id=#{empId} </update>
對應關系
2.6 Map類型參數
Mapper接口中抽象方法的聲明
int updateEmployeeByMap(Map<String, Object> paramMap);
SQL語句
<update id="updateEmployeeByMap">update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}</update>
junit測試
private SqlSession session; //junit5會在每一個@Test方法前執行@BeforeEach方法 @BeforeEach public void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession(); }@Test public void testUpdateEmpNameByMap() {EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);Map<String, Object> paramMap = new HashMap<>();paramMap.put("empSalaryKey", 999.99);paramMap.put("empIdKey", 5);int result = mapper.updateEmployeeByMap(paramMap);log.info("result = " + result); }//junit5會在每一個@Test方法后執行@@AfterEach方法 @AfterEach public void clear() {session.commit();session.close(); }
對應關系
#{}中寫Map中的key
使用場景
有很多零散的參數需要傳遞,但是沒有對應的實體類類型可以使用。使用@Param注解一個一個傳入又太麻煩了。所以都封裝到Map中。
3. 數據輸出
3.1 輸出概述
數據輸出總體上有兩種形式:
-
增刪改操作返回的受影響行數:直接使用 int 或 long 類型接收即可
-
查詢操作的查詢結果
我們需要做的是,指定查詢的輸出數據類型即可!
并且插入場景下,實現主鍵數據回顯示!
Connection conn = null ;PreparedStatement psmt = null ;// Statement PreparedStatement CallableStatementpsmt = conn.prepareStatement("sql" , Statement.RETURN_GENERATED_KEYS);int count = psmt.executeUpdate(); //返回的是影響行數//如果是insert,而且數據庫是自增列。如何獲取自增列的值呢?ResultSet rs = psmt.getGeneratedKeys();long id = rs.getLong(1); //獲取自增列的值//為什么需要獲取自增列的值?//場景: 確認訂單這個動作中,至少包含兩大操作:1) 向訂單表insert一條記錄 2) 向訂單詳情表添加n條記錄// 訂單詳情表中有一列:訂單id號,是一個外鍵,指向訂單表的id// 因此第二步操作時,必須知道第一步返回的自增列的值/*訂單表:id userid money orderDate1 u001 99 2023-10-07 13:50:302 u002 15 2023-10-07 13:51:35訂單詳情表:id productId price productCount orderId1 p001 9 2 12 p002 5 1 13 p006 8 5 1*/
3.2 單個簡單類型
Mapper接口中的抽象方法
int selectEmpCount();
SQL語句
<select id="selectEmpCount" resultType="int">select count(*) from t_emp </select>
Mybatis 內部給常用的數據類型設定了很多別名。 以 int 類型為例,可以寫的名稱有:int、integer、Integer、java.lang.Integer、Int、INT、INTEGER 等等。
junit測試
@Testpublic void testEmpCount() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);int count = employeeMapper.selectEmpCount();log.info("count = " + count);}
細節解釋:
select標簽,通過resultType指定查詢返回值類型!
resultType = "全限定符 | 別名 | 如果是返回集合類型,寫范型類型即可"
別名問題:
https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases
類型別名可為 Java 類型設置一個縮寫名字。 它僅用于 XML 配置,意在降低冗余的全限定類名書寫。例如:
<typeAliases><typeAlias alias="Author" type="domain.blog.Author"/><typeAlias alias="Blog" type="domain.blog.Blog"/> </typeAliases>
當這樣配置時,Blog
可以用在任何使用 domain.blog.Blog
的地方。
也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:
<typeAliases> <package name="domain.blog"/> </typeAliases>
每一個在包 domain.blog
中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。 比如 domain.blog.Author
的別名為 author
;若有注解,則別名為其注解值。見下面的例子:
@Alias("author") public class Author {... }
下面是Mybatis為常見的 Java 類型內建的類型別名。它們都是不區分大小寫的,注意,為了應對原始類型的命名重復,采取了特殊的命名風格。
別名 | 映射的類型 |
---|---|
_byte | byte |
_char (since 3.5.10) | char |
_character (since 3.5.10) | char |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
char (since 3.5.10) | Character |
character (since 3.5.10) | Character |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
biginteger | BigInteger |
object | Object |
object[] | Object[] |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
3.3 返回實體類對象
Mapper接口的抽象方法
Employee selectEmployee(Integer empId);
SQL語句
<!-- 編寫具體的SQL語句,使用id屬性唯一的標記一條SQL語句 --> <!-- resultType屬性:指定封裝查詢結果的Java實體類的全類名 --> <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee"><!-- Mybatis負責把SQL語句中的#{}部分替換成“?”占位符 --><!-- 給每一個字段設置一個別名,讓別名和Java實體類中屬性名一致 -->select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}</select>
通過給數據庫表字段加別名,讓查詢結果的每一列都和Java實體類中屬性對應起來。
增加全局配置自動識別對應關系
在 Mybatis 全局配置文件中,做了下面的配置,select語句中可以不給字段設置別名
<!-- 在全局范圍內對Mybatis進行配置 --> <settings><!-- 具體配置 --><!-- 從org.apache.ibatis.session.Configuration類中可以查看能使用的配置項 --><!-- 將mapUnderscoreToCamelCase屬性配置為true,表示開啟自動映射駝峰式命名規則 --><!-- 規則要求數據庫表字段命名方式:單詞_單詞 --><!-- 規則要求Java實體類屬性名命名方式:首字母小寫的駝峰式命名 --><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
3.4 返回Map類型
適用于SQL查詢返回的各個字段綜合起來并不和任何一個現有的實體類對應,沒法封裝到實體類對象中。能夠封裝成實體類類型的,就不使用Map類型。
Mapper接口的抽象方法
Map<String,Object> selectEmpNameAndMaxSalary();
SQL語句
<!-- Map<String,Object> selectEmpNameAndMaxSalary(); --> <!-- 返回工資最高的員工的姓名和他的工資 --> <select id="selectEmpNameAndMaxSalary" resultType="map">SELECTemp_name 員工姓名,emp_salary 員工工資,(SELECT AVG(emp_salary) FROM t_emp) 部門平均工資FROM t_emp WHERE emp_salary=(SELECT MAX(emp_salary) FROM t_emp) </select><!-- 或者: 查詢部門ID號以及部門的平均薪資 --> <!--select deptNo , avg(sal) as avg_sal from emp group by deptNodeptNo , avg_sald001 1500d002 1750d003 1600-->
junit測試
@Test public void testQueryEmpNameAndSalary() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();for (Map.Entry<String, Object> entry : entrySet) {String key = entry.getKey();Object value = entry.getValue();log.info(key + "=" + value);} }
3.5 返回List類型
查詢結果返回多個實體類對象,希望把多個實體類對象放在List集合中返回。此時不需要任何特殊處理,在resultType屬性中還是設置實體類類型即可。
Mapper接口中抽象方法
List<Employee> selectAll();
SQL語句
<!-- List<Employee> selectAll(); --> <select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">select emp_id empId,emp_name empName,emp_salary empSalaryfrom t_emp </select>
junit測試
@Test public void testSelectAll() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);List<Employee> employeeList = employeeMapper.selectAll();for (Employee employee : employeeList) {log.info("employee = " + employee);} }
3.6 返回主鍵值
-
自增長類型主鍵
Mapper接口中的抽象方法
int insertEmployee(Employee employee);
SQL語句
<!-- int insertEmployee(Employee employee); --> <!-- useGeneratedKeys屬性字面意思就是“使用生成的主鍵” --> <!-- keyProperty屬性可以指定主鍵在實體類對象中對應的屬性名,Mybatis會將拿到的主鍵值存入這個屬性 --> <insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">insert into t_emp(emp_name,emp_salary)values(#{empName},#{empSalary}) </insert>
junit測試
@Test public void testSaveEmp() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);Employee employee = new Employee();employee.setEmpName("john");employee.setEmpSalary(666.66);employeeMapper.insertEmployee(employee);log.info("employee.getEmpId() = " + employee.getEmpId()); }
注意
Mybatis是將自增主鍵的值設置到實體類對象中,而不是以Mapper接口方法返回值的形式返回。
-
非自增長類型主鍵
而對于不支持自增型主鍵的數據庫(例如 Oracle)或者字符串類型主鍵,則可以使用 selectKey 子元素:selectKey 元素將會首先運行,id 會被設置,然后插入語句會被調用!
使用
selectKey
幫助插入UUID作為字符串類型主鍵示例:<insert id="insertUser" parameterType="User"><selectKey keyProperty="id" resultType="java.lang.String"order="BEFORE">SELECT UUID() as id</selectKey>INSERT INTO user (id, username, password) VALUES (#{id},#{username},#{password}) </insert>
在上例中,我們定義了一個
insertUser
的插入語句來將User
對象插入到user
表中。我們使用selectKey
來查詢 UUID 并設置到id
字段中。通過
keyProperty
屬性來指定查詢到的 UUID 賦值給對象中的id
屬性,而resultType
屬性指定了 UUID 的類型為java.lang.String
。需要注意的是,我們將
selectKey
放在了插入語句的前面,這是因為 MySQL 在insert
語句中只支持一個select
子句,而selectKey
中查詢 UUID 的語句就是一個select
子句,因此我們需要將其放在前面。最后,在將
User
對象插入到user
表中時,我們直接使用對象中的id
屬性來插入主鍵值。使用這種方式,我們可以方便地插入 UUID 作為字符串類型主鍵。當然,還有其他插入方式可以使用,如使用Java代碼生成UUID并在類中顯式設置值等。需要根據具體應用場景和需求選擇合適的插入方式。
3.7 實體類屬性和數據庫字段對應關系
-
別名對應
將字段的別名設置成和實體類屬性一致。
<!-- 編寫具體的SQL語句,使用id屬性唯一的標記一條SQL語句 --> <!-- resultType屬性:指定封裝查詢結果的Java實體類的全類名 --> <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee"><!-- Mybatis負責把SQL語句中的#{}部分替換成“?”占位符 --><!-- 給每一個字段設置一個別名,讓別名和Java實體類中屬性名一致省略了as的 -->select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}</select>
關于實體類屬性的約定:?getXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小寫。
-
全局配置自動識別駝峰式命名規則
在Mybatis全局配置文件加入如下配置:
<!-- 使用settings對Mybatis全局進行設置 --> <settings><!-- 將xxx_xxx這樣的列名自動映射到xxXxx這樣駝峰式命名的屬性名 --><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
SQL語句中可以不使用別名
<!-- Employee selectEmployee(Integer empId); --> <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee"> select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId} </select>
-
使用resultMap
使用resultMap標簽定義對應關系,再在后面的SQL語句中引用這個對應關系
<!-- 專門聲明一個resultMap設定column到property之間的對應關系 --><resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee" ><!-- 使用id標簽設置主鍵列和主鍵屬性之間的對應關系 --><!-- column屬性用于指定字段名;property屬性用于指定Java實體類屬性名 --><id column="emp_id" property="empId"/><!-- 使用result標簽設置普通字段和Java實體類屬性之間的關系 --><result column="emp_name" property="empName"/><result column="emp_salary" property="empSalary"/></resultMap><!-- Employee selectEmployeeByRM(Integer empId); --><select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}</select>
4. CRUD強化練習
-
準備數據庫數據
首先,我們需要準備一張名為
user
的表。該表包含字段 id(主鍵)、username、password。創建SQL如下:CREATE TABLE `user` (`id` INT(11) NOT NULL AUTO_INCREMENT,`username` VARCHAR(50) NOT NULL,`password` VARCHAR(50) NOT NULL,PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-
實體類準備
接下來,我們需要定義一個實體類
User
,來對應 user 表的一行數據。@Data public class User {private Integer id;private String username;private String password; }
-
Mapper接口定義
定義一個 Mapper 接口
UserMapper
,并在其中添加 user 表的增、刪、改、查方法。public interface UserMapper {int insert(User user);int update(User user);int delete(Integer id);User selectById(Integer id);List<User> selectAll(); }
-
MapperXML編寫
在 resources /mappers目錄下創建一個名為
UserMapper.xml
的 XML 文件,包含與 Mapper 接口中相同的五個 SQL 語句,并在其中,將查詢結果映射到User
實體中。<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- namespace等于mapper接口類的全限定名,這樣實現對應 --> <mapper namespace="com.atguigu.mapper.UserMapper"><!-- 定義一個插入語句,并獲取主鍵值 --><insert id="insert" useGeneratedKeys="true" keyProperty="id">INSERT INTO user(username, password)VALUES(#{username}, #{password})</insert><update id="update">UPDATE user SET username=#{username}, password=#{password}WHERE id=#{id}</update><delete id="delete">DELETE FROM user WHERE id=#{id}</delete><!-- resultType使用user別名,稍后需要配置!--><select id="selectById" resultType="user">SELECT id, username, password FROM user WHERE id=#{id}</select><!-- resultType返回值類型為集合,所以只寫范型即可! --><select id="selectAll" resultType="user">SELECT id, username, password FROM user</select></mapper>
-
MyBatis配置文件
位置:resources: mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><!-- 開啟駝峰式映射--><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 開啟logback日志輸出--><setting name="logImpl" value="SLF4J"/></settings><typeAliases><!-- 給實體類起別名 --><package name="com.atguigu.pojo"/></typeAliases><!-- environments表示配置Mybatis的開發環境,可以配置多個環境,在眾多具體環境中,使用default屬性指定實際運行時使用的環境。default屬性的取值是environment標簽的id屬性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一個具體的環境 --><environment id="development"><!-- Mybatis的內置的事務管理器 --><transactionManager type="JDBC"/><!-- 配置數據源 --><dataSource type="POOLED"><!-- 建立數據庫連接的具體信息 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/><property name="username" value="root"/><property name="password" value="root"/></dataSource></environment></environments><mappers><!-- Mapper注冊:指定Mybatis映射文件的具體位置 --><!-- mapper標簽:配置一個具體的Mapper映射文件 --><!-- resource屬性:指定Mapper映射文件的實際存儲位置,這里需要使用一個以類路徑根目錄為基準的相對路徑 --><!-- 對Maven工程的目錄結構來說,resources目錄下的內容會直接放入類路徑,所以這里我們可以以resources目錄為基準 --><mapper resource="mappers/UserMapper.xml"/></mappers></configuration>
-
效果測試
package com.atguigu.test;import com.atguigu.mapper.UserMapper; import com.atguigu.pojo.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test;import java.io.IOException; import java.util.List;/*** projectName: com.atguigu.test*/ public class MyBatisTest {private SqlSession session;// junit會在每一個@Test方法前執行@BeforeEach方法@BeforeEachpublic void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession();}@Testpublic void createTest() {User user = new User();user.setUsername("admin");user.setPassword("123456");UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.insert(user);System.out.println(user);}@Testpublic void updateTest() {UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.selectById(1);user.setUsername("root");user.setPassword("111111");userMapper.update(user);user = userMapper.selectById(1);System.out.println(user);}@Testpublic void deleteTest() {UserMapper userMapper = session.getMapper(UserMapper.class);userMapper.delete(1);User user = userMapper.selectById(1);System.out.println("user = " + user);}@Testpublic void selectByIdTest() {UserMapper userMapper = session.getMapper(UserMapper.class);User user = userMapper.selectById(1);System.out.println("user = " + user);}@Testpublic void selectAllTest() {UserMapper userMapper = session.getMapper(UserMapper.class);List<User> userList = userMapper.selectAll();System.out.println("userList = " + userList);}// junit會在每一個@Test方法后執行@@AfterEach方法@AfterEachpublic void clear() {session.commit();session.close();} }
5. mapperXML標簽總結
MyBatis 的真正強大在于它的語句映射,這是它的魔力所在。由于它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 致力于減少使用成本,讓用戶能更專注于 SQL 代碼。
SQL 映射文件只有很少的幾個頂級元素(按照應被定義的順序列出):
-
insert
– 映射插入語句。 -
update
– 映射更新語句。 -
delete
– 映射刪除語句。 -
select
– 映射查詢語句。
select標簽:
MyBatis 在查詢和結果映射做了相當多的改進。一個簡單查詢的 select 元素是非常簡單:
<select id="selectPerson" resultType="hashmap"> SELECT * FROM PERSON WHERE ID = #{id} </select>
這個語句名為 selectPerson,接受一個 int(或 Integer)類型的參數,并返回一個 HashMap 類型的對象,其中的鍵是列名,值便是結果行中的對應值。
注意參數符號:#{id}
MyBatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,并被傳遞到一個新的預處理語句中,就像這樣:
// 近似的 JDBC 代碼,非 MyBatis 代碼... String selectPerson = "SELECT * FROM PERSON WHERE ID=?"; PreparedStatement ps = conn.prepareStatement(selectPerson); ps.setInt(1,id);
select 元素允許你配置很多屬性來配置每條語句的行為細節:
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句。 |
resultType | 期望從這條語句中返回結果的類全限定名或別名。 注意,如果返回的是集合,那應該設置為集合包含的類型,而不是集合本身的類型。 resultType 和 resultMap 之間只能同時使用一個。 |
resultMap | 對外部 resultMap 的命名引用。結果映射是 MyBatis 最強大的特性,如果你對其理解透徹,許多復雜的映射問題都能迎刃而解。 resultType 和 resultMap 之間只能同時使用一個。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴數據庫驅動)。 |
statementType | 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
insert, update 和 delete標簽:
數據變更語句 insert,update 和 delete 的實現非常接近:
<insertid="insertAuthor"statementType="PREPARED"keyProperty=""keyColumn=""useGeneratedKeys=""timeout="20"><updateid="updateAuthor"statementType="PREPARED"timeout="20"><deleteid="deleteAuthor"statementType="PREPARED"timeout="20">
屬性 | 描述 |
---|---|
id | 在命名空間中唯一的標識符,可以被用來引用這條語句。 |
timeout | 這個設置是在拋出異常之前,驅動程序等待數據庫返回請求結果的秒數。默認值為未設置(unset)(依賴數據庫驅動)。 |
statementType | 可選 STATEMENT,PREPARED 或 CALLABLE。這會讓 MyBatis 分別使用 Statement,PreparedStatement 或 CallableStatement,默認值:PREPARED。 |
useGeneratedKeys | (僅適用于 insert 和 update)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數據庫內部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關系型數據庫管理系統的自動遞增字段),默認值:false。 |
keyProperty | (僅適用于 insert 和 update)指定能夠唯一識別對象的屬性,MyBatis 會使用 getGeneratedKeys 的返回值或 insert 語句的 selectKey 子元素設置它的值,默認值:未設置(unset )。如果生成列不止一個,可以用逗號分隔多個屬性名稱。 |
keyColumn | (僅適用于 insert 和 update)設置生成鍵值在表中的列名,在某些數據庫(像 PostgreSQL)中,當主鍵列不是表中的第一列的時候,是必須設置的。如果生成列不止一個,可以用逗號分隔多個屬性名稱。 |
6.mybatis配置文件設計標簽和頂層結構如下:
-
configuration(配置)
-
properties(屬性)
-
settings(設置)
-
typeAliases(類型別名)
-
typeHandlers(類型處理器)
-
objectFactory(對象工廠)
-
plugins(插件)
-
environments(環境配置)
-
environment(環境變量)
-
transactionManager(事務管理器)
-
dataSource(數據源)
-
-
-
databaseIdProvider(數據庫廠商標識)
-
mappers(映射器)
-
settings設置項:
設置名 | 描述 | 有效值 | 默認值 |
---|---|---|---|
cacheEnabled | 全局性地開啟或關閉所有映射器配置文件中已配置的任何緩存。 | true | false | true |
lazyLoadingEnabled | 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設置 fetchType 屬性來覆蓋該項的開關狀態。 | true | false | false |
aggressiveLazyLoading | 開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載(參考 lazyLoadTriggerMethods )。 | true | false | false (在 3.4.1 及之前的版本中默認為 true) |
multipleResultSetsEnabled | 是否允許單個語句返回多結果集(需要數據庫驅動支持)。 | true | false | true |
useColumnLabel | 使用列標簽代替列名。實際表現依賴于數據庫驅動,具體可參考數據庫驅動的相關文檔,或通過對比測試來觀察。 | true | false | true |
useGeneratedKeys | 允許 JDBC 支持自動生成主鍵,需要數據庫驅動支持。如果設置為 true,將強制使用自動生成主鍵。盡管一些數據庫驅動不支持此特性,但仍可正常工作(如 Derby)。 | true | false | False |
autoMappingBehavior | 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示關閉自動映射;PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 FULL 會自動映射任何復雜的結果集(無論是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
autoMapping UnknownColumnBehavior | 指定發現自動映射目標未知列(或未知屬性類型)的行為。 * NONE : 不做任何反應 * WARNING : 輸出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等級必須設置為 WARN ) * FAILING : 映射失敗 (拋出 SqlSessionException ) | NONE, WARNING, FAILING | NONE |
safeRowBoundsEnabled | 是否允許在嵌套語句中使用分頁(RowBounds)。如果允許使用則設置為 false。 | true | false | False |
safeResultHandlerEnabled | 是否允許在嵌套語句中使用結果處理器(ResultHandler)。如果允許使用則設置為 false。 | true | false | True |
mapUnderscoreToCamelCase | 是否開啟駝峰命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn。 | true | false | False |
localCacheScope | MyBatis 利用本地緩存機制(Local Cache)防止循環引用和加速重復的嵌套查詢。 默認值為 SESSION,會緩存一個會話中執行的所有查詢。 若設置值為 STATEMENT,本地緩存將僅用于執行語句,對相同 SqlSession 的不同查詢將不會進行緩存。 | SESSION | STATEMENT | SESSION |
jdbcTypeForNull | 當沒有為參數指定特定的 JDBC 類型時,空值的默認 JDBC 類型。 某些數據庫驅動需要指定列的 JDBC 類型,多數情況直接用一般類型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER |
lazyLoadTriggerMethods | 指定對象的哪些方法觸發一次延遲加載。 | 用逗號分隔的方法列表。 | equals,clone,hashCode,toString |
defaultScriptingLanguage | 指定動態 SQL 生成使用的默認腳本語言。 | 一個類型別名或全限定類名。 | org.apache.ibatis.scripting.xmltags.XMLLanguageDriver |
defaultEnumTypeHandler | 指定 Enum 使用的默認 TypeHandler 。(新增于 3.4.5) | 一個類型別名或全限定類名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這在依賴于 Map.keySet() 或 null 值進行初始化時比較有用。注意基本類型(int、boolean 等)是不能設置成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 當返回行的所有列都是空時,MyBatis默認返回 null 。 當開啟這個設置時,MyBatis會返回一個空實例。 請注意,它也適用于嵌套的結果集(如集合或關聯)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名稱的前綴。 | 任何字符串 | 未設置 |
logImpl | 指定 MyBatis 所用日志的具體實現,未指定時將自動查找。 | SLF4J | LOG4J(3.5.9 起廢棄) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未設置 |
proxyFactory | 指定 Mybatis 創建可延遲加載對象所用到的代理工具。 | CGLIB (3.5.10 起廢棄) | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的實現 | 自定義 VFS 的實現的類全限定名,以逗號分隔。 | 未設置 |
useActualParamName | 允許使用方法簽名中的名稱作為語句參數名稱。 為了使用該特性,你的項目必須采用 Java 8 編譯,并且加上 -parameters 選項。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一個提供 Configuration 實例的類。 這個被返回的 Configuration 實例用來加載被反序列化對象的延遲加載屬性值。 這個類必須包含一個簽名為static Configuration getConfiguration() 的方法。(新增于 3.2.3) | 一個類型別名或完全限定類名。 | 未設置 |
shrinkWhitespacesInSql | 從SQL中刪除多余的空格字符。請注意,這也會影響SQL中的文字字符串。 (新增于 3.5.5) | true | false | false |
defaultSqlProviderType | 指定一個擁有 provider 方法的 sql provider 類 (新增于 3.5.6). 這個類適用于指定 sql provider 注解上的type (或 value ) 屬性(當這些屬性在注解中被忽略時)。 (e.g. @SelectProvider ) | 類型別名或者全限定名 | 未設置 |
nullableOnForEach | 為 'foreach' 標簽的 'nullable' 屬性指定默認值。(新增于 3.5.9) | true | false | false |
argNameBased ConstructorAutoMapping | 當應用構造器自動映射時,參數名稱被用來搜索要映射的列,而不再依賴列的順序。(新增于 3.5.10) | true | false | false |
四、MyBatis多表映射
回顧數據庫知識: 1) 實體(Entity)和實體之間的關系一對一、一對多、多對多一對一主鍵關聯:人員基本信息表 、 身份證信息維護表id(pk) name id(pk) cardNo 1 jim 1 3212811999100112322 tom 2 一對一唯一外鍵關聯男生表 、 女生表boyid boyname girlid girlname bodyID(unique)1 jim 5 lina 12 tom 6 rose 2 2) 數據庫的設計范式第一范式:列不可再分假設有一列:收貨地址。這一列的值是:廣東省深圳市寶安區航城街道西部硅谷尚某谷分谷第二范式:一張表只描述一件事假設有學員表,其中包含如下的列: 學號、姓名、出生年月、性別、籍貫、父母姓名、血型我認為學號、姓名、出生年月、性別 這四個作為學員表是必備的。但是父母姓名、父母學歷、父母的工作性質等這些信息并不是學員本身的信息。因此這張學員表不符合第二范式。應該拆分成兩張表。第三范式:表中的每一列和主鍵是直接相關,而不是間接相關。假設有學員成績表,不符合第三范式的表設計如下: id stuid stuname subjectid subjectname score1 s001 jim sub001 語文 90上面這張表不符合第三范式,因為學員的姓名以及科目名稱都可以通過他們的id和學員表以及科目表進行表連接查詢到。符合第三范式的設計方案應該如下:id stuid subid score1 s001 sub001 90 3) 數據庫范式和數據庫的查詢性能之間的權衡我們到底是想讓數據庫設計的更規范,還是想讓數據庫的查詢性能更高?就拿上面的成績表的例子來說,我們應該降低數據庫的范式,從而獲得更好的查詢性能。相反的,對于一些固定不變的,數據量較小的,我們應該盡可能的提高數據庫的規范度,從而最大程度減少冗余(性能方面可以通過其他技術手段解決,例如緩存) 4) 表連接的基礎知識點:左外連接、右外連接、內連接、全連接(mysql不支持)mysql> create database mydb charset utf8; Query OK, 1 row affected, 1 warning (0.03 sec)mysql> use mydb; Database changed mysql> show tables; Empty set (0.01 sec)mysql> create table t1-> (-> c1 varchar(5),-> c2 varchar(5)-> ); Query OK, 0 rows affected (0.07 sec)mysql> create table t2-> (-> c3 varchar(5),-> c4 varchar(5)-> ); Query OK, 0 rows affected (0.04 sec)mysql> insert into t1 values-> ('a','b'),('b','c'),('c','d'); Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0mysql> insert into t2 values-> ('b','d'),('e','f'); Query OK, 2 rows affected (0.02 sec) Records: 2 Duplicates: 0 Warnings: 0表連接的基本語句如下: mysql> select * from t1 , t2 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | a | b | e | f | | a | b | b | d | | b | c | e | f | | b | c | b | d | | c | d | e | f | | c | d | b | d | +------+------+------+------+ 6 rows in set (0.00 sec)mysql> select * from t1 inner join t2 on 1 = 1 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | a | b | e | f | | a | b | b | d | | b | c | e | f | | b | c | b | d | | c | d | e | f | | c | d | b | d | +------+------+------+------+ 6 rows in set (0.00 sec)mysql> select * from t1 inner join t2 on c1 = c3 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | b | c | b | d | +------+------+------+------+ 1 row in set (0.00 sec)mysql> select * from t1 left join t2 on c1 = c3 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | a | b | NULL | NULL | | b | c | b | d | | c | d | NULL | NULL | +------+------+------+------+ 3 rows in set (0.00 sec)mysql> select * from t1 right join t2 on c1 = c3 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | b | c | b | d | | NULL | NULL | e | f | +------+------+------+------+ 2 rows in set (0.00 sec)mysql> select * from t1 full join t2 on c1 = c3 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | b | c | b | d | +------+------+------+------+ 1 row in set (0.00 sec)mysql> select * from t1 left join t2 on c1 = c3-> union-> select * from t1 right join t2 on c1 = c3 ; +------+------+------+------+ | c1 | c2 | c3 | c4 | +------+------+------+------+ | a | b | NULL | NULL | | b | c | b | d | | c | d | NULL | NULL | | NULL | NULL | e | f | +------+------+------+------+ 4 rows in set (0.01 sec)
mybatis中對于對象關聯分為兩大類:對一關聯、對多關聯
對多關聯: 一個學員擁有多本書。 Student、Book Student:sid , sname , bookList Book: bid , bname
對一關聯:一本書只屬于一個學員 Student、Book Student:sid , sname , bookList Book: bid , bname , student
1. 多表映射概念
-
多表查詢結果映射思路
上面課程中,我全面講解了單表的mybatis操作!但是開發中更多的是多表查詢需求,這種情況我們如何讓進行處理?
MyBatis 思想是:數據庫不可能永遠是你所想或所需的那個樣子。 我們希望每個數據庫都具備良好的第三范式或 BCNF 范式,可惜它們并不都是那樣。 如果能有一種數據庫映射模式,完美適配所有的應用程序查詢需求,那就太好了,而 ResultMap 就是 MyBatis 的完美答案。
官方例子:我們如何映射下面這個語句?
<!-- 非常復雜的語句 --> <select id="selectBlogDetails" resultMap="detailedBlogResultMap">selectB.id as blog_id,B.title as blog_title,B.author_id as blog_author_id,A.id as author_id,A.username as author_username,A.password as author_password,A.email as author_email,A.bio as author_bio,A.favourite_section as author_favourite_section,P.id as post_id,P.blog_id as post_blog_id,P.author_id as post_author_id,P.created_on as post_created_on,P.section as post_section,P.subject as post_subject,P.draft as draft,P.body as post_body,C.id as comment_id,C.post_id as comment_post_id,C.name as comment_name,C.comment as comment_text,T.id as tag_id,T.name as tag_namefrom Blog Bleft outer join Author A on B.author_id = A.idleft outer join Post P on B.id = P.blog_idleft outer join Comment C on P.id = C.post_idleft outer join Post_Tag PT on PT.post_id = P.idleft outer join Tag T on PT.tag_id = T.idwhere B.id = #{id} </select>
你可能想把它映射到一個智能的對象模型,這個對象表示了一篇博客,它由某位作者所寫,有很多的博文,每篇博文有零或多條的評論和標簽。 我們先來看看下面這個完整的例子,它是一個非常復雜的結果映射(假設作者,博客,博文,評論和標簽都是類型別名)。 雖然它看起來令人望而生畏,但其實非常簡單。
<!-- 非常復雜的結果映射 --> <resultMap id="detailedBlogResultMap" type="Blog"><constructor><idArg column="blog_id" javaType="int"/></constructor><result property="title" column="blog_title"/><association property="author" javaType="Author"><id property="id" column="author_id"/><result property="username" column="author_username"/><result property="password" column="author_password"/><result property="email" column="author_email"/><result property="bio" column="author_bio"/><result property="favouriteSection" column="author_favourite_section"/></association><collection property="posts" ofType="Post"><id property="id" column="post_id"/><result property="subject" column="post_subject"/><association property="author" javaType="Author"/><collection property="comments" ofType="Comment"><id property="id" column="comment_id"/></collection><collection property="tags" ofType="Tag" ><id property="id" column="tag_id"/></collection></collection> </resultMap>
你現在可能看不懂,接下來我們要學習將多表查詢結果使用ResultMap標簽映射到實體類對象上!
我們的學習目標:
多表查詢語句復習
多表結果承接實體類設計
使用ResultMap完成多表結果映射
-
實體類設計方案
多表關系回顧:(雙向查看)
-
一對一
夫妻關系,人和身份證號
-
-
一對多
用戶和用戶的訂單,鎖和鑰匙
-
多對多
老師和學生,部門和員工?實體類設計關系:(單向查看)
-
-
對一 : 夫妻一方對應另一方,訂單對應用戶都是對一關系
實體類設計:對一關系下,類中只要包含單個對方對象類型屬性即可!
例如:? ```java? public class Customer {
private Integer customerId;private String customerName;}public class Order {private Integer orderId;private String orderName;private Customer customer;// 體現的是對一的關系}
-
對多: 用戶對應的訂單,講師對應的學生或者學生對應的講師都是對多關系:
實體類設計:對多關系下,類中只要包含對方類型集合屬性即可!
public class Customer {private Integer customerId;private String customerName;private List<Order> orderList;// 體現的是對多的關系 }public class Order {private Integer orderId;private String orderName;private Customer customer;// 體現的是對一的關系}
多表結果實體類設計小技巧:? 對一,屬性中包含對方對象? 對多,屬性中包含對方對象集合只有真實發生多表查詢時,才需要設計和修改實體類,否則不提前設計和修改實體類!? 無論多少張表聯查,實體類設計都是兩兩考慮!? 在查詢映射的時候,只需要關注本次查詢相關的屬性!例如:查詢訂單和對應的客戶,就不要關注客戶中的訂單集合!
-
-
多表映射案例準備
數據庫:
CREATE TABLE `t_customer` (`customer_id` INT NOT NULL AUTO_INCREMENT, `customer_name` CHAR(100), PRIMARY KEY (`customer_id`) );CREATE TABLE `t_order` ( `order_id` INT NOT NULL AUTO_INCREMENT, `order_name` CHAR(100), `customer_id` INT, PRIMARY KEY (`order_id`) ); INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1'); INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1'); INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1');
實際開發時,一般在開發過程中,不給數據庫表設置外鍵約束。?原因是避免調試不方便。?一般是功能開發完成,再加外鍵約束檢查是否有bug。
實體類設計:
稍后會進行訂單關聯客戶查詢,也會進行客戶關聯訂單查詢,所以在這先練習設計
@Data public class Customer {private Integer customerId;private String customerName;private List<Order> orderList;// 體現的是對多的關系} @Data public class Order {private Integer orderId;private String orderName;private Customer customer;// 體現的是對一的關系}
2. 對一映射
-
需求說明
根據ID查詢訂單,以及訂單關聯的用戶的信息!
-
OrderMapper接口
public interface OrderMapper {Order selectOrderWithCustomer(Integer orderId); }
-
OrderMapper.xml配置文件
<!-- 創建resultMap實現“對一”關聯關系映射 --> <!-- id屬性:通常設置為這個resultMap所服務的那條SQL語句的id加上“ResultMap” --> <!-- type屬性:要設置為這個resultMap所服務的那條SQL語句最終要返回的類型 --> <resultMap id="selectOrderWithCustomerResultMap" type="order"><!-- 先設置Order自身屬性和字段的對應關系 --><id column="order_id" property="orderId"/><result column="order_name" property="orderName"/><!-- 使用association標簽配置“對一”關聯關系 --><!-- property屬性:在Order類中對一的一端進行引用時使用的屬性名 --><!-- javaType屬性:一的一端類的全類名 --><association property="customer" javaType="customer"><!-- 配置Customer類的屬性和字段名之間的對應關系 --><id column="customer_id" property="customerId"/><result column="customer_name" property="customerName"/></association></resultMap><!-- Order selectOrderWithCustomer(Integer orderId); --> <select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">SELECT order_id,order_name,c.customer_id,customer_nameFROM t_order oLEFT JOIN t_customer cON o.customer_id=c.customer_idWHERE o.order_id=#{orderId}</select>
對應關系可以參考下圖:
-
Mybatis全局注冊Mapper文件
<!-- 注冊Mapper配置文件:告訴Mybatis我們的Mapper配置文件的位置 --> <mappers><!-- 在mapper標簽的resource屬性中指定Mapper配置文件以“類路徑根目錄”為基準的相對路徑 --><mapper resource="mappers/OrderMapper.xml"/></mappers>
-
junit測試程序
@Slf4j public class MyBatisTest {private SqlSession session;// junit會在每一個@Test方法前執行@BeforeEach方法@BeforeEachpublic void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession();}@Testpublic void testRelationshipToOne() {OrderMapper orderMapper = session.getMapper(OrderMapper.class);// 查詢Order對象,檢查是否同時查詢了關聯的Customer對象Order order = orderMapper.selectOrderWithCustomer(2);log.info("order = " + order);}// junit會在每一個@Test方法后執行@@AfterEach方法@AfterEachpublic void clear() {session.commit();session.close();} }
-
關鍵詞
在“對一”關聯關系中,我們的配置比較多,但是關鍵詞就只有:association和javaType
3. 對多映射
-
需求說明
查詢客戶和客戶關聯的訂單信息!
-
CustomerMapper接口
public interface CustomerMapper {Customer selectCustomerWithOrderList(Integer customerId);}
-
CustomerMapper.xml文件
<!-- 配置resultMap實現從Customer到OrderList的“對多”關聯關系 --> <resultMap id="selectCustomerWithOrderListResultMap"type="customer"><!-- 映射Customer本身的屬性 --><id column="customer_id" property="customerId"/><result column="customer_name" property="customerName"/><!-- collection標簽:映射“對多”的關聯關系 --><!-- property屬性:在Customer類中,關聯“多”的一端的屬性名 --><!-- ofType屬性:集合屬性中元素的類型 --><collection property="orderList" ofType="order"><!-- 映射Order的屬性 --><id column="order_id" property="orderId"/><result column="order_name" property="orderName"/></collection></resultMap><!-- Customer selectCustomerWithOrderList(Integer customerId); --> <select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">SELECT c.customer_id,c.customer_name,o.order_id,o.order_nameFROM t_customer cLEFT JOIN t_order oON c.customer_id=o.customer_idWHERE c.customer_id=#{customerId} </select>
對應關系可以參考下圖:
-
Mybatis全局注冊Mapper文件
<!-- 注冊Mapper配置文件:告訴Mybatis我們的Mapper配置文件的位置 --> <mappers><!-- 在mapper標簽的resource屬性中指定Mapper配置文件以“類路徑根目錄”為基準的相對路徑 --><mapper resource="mappers/OrderMapper.xml"/><mapper resource="mappers/CustomerMapper.xml"/> </mappers>
-
junit測試程序
@Test public void testRelationshipToMulti() {CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);// 查詢Customer對象同時將關聯的Order集合查詢出來Customer customer = customerMapper.selectCustomerWithOrderList(1);log.info("customer.getCustomerId() = " + customer.getCustomerId());log.info("customer.getCustomerName() = " + customer.getCustomerName());List<Order> orderList = customer.getOrderList();for (Order order : orderList) {log.info("order = " + order);} }
-
關鍵詞
在“對多”關聯關系中,同樣有很多配置,但是提煉出來最關鍵的就是:“collection”和“ofType”
4. 多對多映射
多對多其實就是兩個對多
多對多是表關系,實體關系單向來看就是對多!只不過需要三表查詢(中間表)!!!
多對多表關系示意圖:
多對多具體業務實現:
-
數據庫腳本
-- 創建講師表 CREATE TABLE t_teacher (t_id INT PRIMARY KEY,t_name VARCHAR(50) );-- 創建學生表 CREATE TABLE t_student (s_id INT PRIMARY KEY,s_name VARCHAR(50) );-- 創建中間表 CREATE TABLE t_inner (t_id INT,s_id INT,PRIMARY KEY (t_id, s_id) );-- 向講師表中插入測試數據 INSERT INTO t_teacher VALUES (1, '張三'); INSERT INTO t_teacher VALUES (2, '李四'); INSERT INTO t_teacher VALUES (3, '王五');-- 向學生表中插入測試數據 INSERT INTO t_student VALUES (1, '小明'); INSERT INTO t_student VALUES (2, '小紅'); INSERT INTO t_student VALUES (3, '小剛');-- 向中間表中插入測試數據 INSERT INTO t_inner VALUES (1, 1); INSERT INTO t_inner VALUES (1, 3); INSERT INTO t_inner VALUES (2, 2); INSERT INTO t_inner VALUES (3, 1); INSERT INTO t_inner VALUES (3, 3);
-
查詢需求分析
查詢講師信息,并且查詢每名講師關聯的學生信息!
-
實體類設計
@Data public class Teacher {private Integer tId;private String tName;private List<Student> students; }@Data public class Student {private Integer sId;private String sName; }
-
TeacherMapper接口
public interface TeacherMapper {List<Teacher> findAllTeachers(); }
-
TeacherMapperXML文件
<mapper namespace="com.example.mapper.TeacherMapper"><select id="findAllTeachers" resultMap="teacherMap">SELECT t.t_id, t.t_name, s.s_id, s.s_nameFROM t_teacher t LEFT OUTER JOIN t_inner i ON t.t_id = i.t_idLEFT OUTER JOIN t_student s ON i.s_id = s.s_id</select><resultMap id="teacherMap" type="teacher"><id property="tId" column="t_id" /><result property="tName" column="t_name" /><collection property="students" ofType="student" ><id property="sId" column="s_id" /><result property="sName" column="s_name" /></collection></resultMap> </mapper>
-
Mybatis全局注冊Mapper文件
<!-- 注冊Mapper配置文件:告訴Mybatis我們的Mapper配置文件的位置 --> <mappers><!-- 在mapper標簽的resource屬性中指定Mapper配置文件以“類路徑根目錄”為基準的相對路徑 --><mapper resource="mappers/OrderMapper.xml"/><mapper resource="mappers/CustomerMapper.xml"/><mapper resource="mappers/TeacherMapper.xml"/> </mappers>
-
junit測試程序
@Test public void testTeacherRelationshipToMulti() {TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);// 查詢Customer對象同時將關聯的Order集合查詢出來List<Teacher> allTeachers = teacherMapper.findAllTeachers();log.info("allTeachers = " + allTeachers);for (Teacher teacher : allTeachers) {log.info("teacher = " + teacher);log.info("teacher.students = " + teacher.getStudents());} }
5. 多表映射總結
5.1 多表映射優化
setting屬性 | 屬性含義 | 可選值 | 默認值 |
---|---|---|---|
autoMappingBehavior | 指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示關閉自動映射;PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 FULL 會自動映射任何復雜的結果集(無論是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL |
我們可以將autoMappingBehavior設置為full,進行多表resultMap映射的時候,可以省略符合列和屬性命名映射規則(列名=屬性名,或者開啟駝峰映射也可以自定映射)的result標簽!
修改mybati-sconfig.xml:
<!--開啟resultMap自動映射 --> <setting name="autoMappingBehavior" value="FULL"/>
修改teacherMapper.xml
<resultMap id="teacherMap" type="teacher"><id property="tId" column="t_id" /><!-- 開啟自動映射,并且開啟駝峰式支持!可以省略 result!--> <!-- <result property="tName" column="t_name" />--><collection property="students" ofType="student" ><id property="sId" column="s_id" /> <!-- <result property="sName" column="s_name" />--></collection> </resultMap>
5.2 多表映射總結
關聯關系 | 配置項關鍵詞 | 所在配置文件和具體位置 |
---|---|---|
對一 | association標簽/javaType屬性/property屬性 | Mapper配置文件中的resultMap標簽內 |
對多 | collection標簽/ofType屬性/property屬性 | Mapper配置文件中的resultMap標簽內 |
5.3 分步查詢
// 在StudentMapper中添加一個方法: public interface StudentMapper {//演示分步查詢,根據sid查詢特定的Student信息,包含Book集合Student getStudentWithBookList(String sid );}
<!-- 在StudentMapper.xml文件中添加如下配置: --> <!-- 演示分步查詢 --> <!--根據sid查詢特定的學員信息,包括他關聯的book集合。上面這個功能其實可以分為兩個步驟:1. 根據sid查詢特定學員本身的信息 - 查學員表select * from t_stu where sid = 12. 根據sid查詢book集合 - 查圖書表select * from t_book where stuid = 1 --> <select id="getStudentWithBookList" resultMap="studentWithBookListResultMap3">select * from t_stu where sid = #{value} </select> <resultMap id="studentWithBookListResultMap3" type="Student"><id property="sid" column="sid"/><collection property="bookList" select="com.atguigu.mybatis.mapper.BookMapper.getBookListBySid" column="sid"/> </resultMap> <!-- 新知識點僅僅是上面的兩個屬性: select , column --> <!-- 上面的select引入的是如下的查詢配置: --> <!-- 在BookMapper.xml文件中添加如下配置: --> <!-- 查詢特定學員的圖書列表 --> <select id="getBookListBySid" resultType="Book">select * from t_book where stuid = #{value} </select>
5.4 延遲加載
<!-- 為什么需要延遲加載 --> <!-- 如何設置延遲加載:--> <!-- 在mybatis-config.xml文件中開啟全局的延遲加載 --> <setting name="lazyLoadingEnabled" value="true"/>
//單元測試方法如下: @Test public void test02(){//System.out.println(studentMapper.getStudentWithBookList("s001"));Student student = studentMapper.getStudentWithBookList("s001");System.out.println(student.getSid()+"-"+student.getSname());//下面這句話如果注釋,那么就不會查詢t_book表System.out.println(student.getBookList().size()); }
延遲加載的前提條件是:分步查詢。
五、MyBatis動態語句(動態SQL)
1. 動態語句需求和簡介
經常遇到很多按照很多查詢條件進行查詢的情況,比如智聯招聘的職位搜索等。其中經常出現很多條件不取值的情況,在后臺應該如何完成最終的SQL語句呢?
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
使用動態 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動態 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基于 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
2. if和where標簽
使用動態 SQL 最常見情景是根據條件包含 where / if 子句的一部分。比如:
<!-- List<Employee> selectEmployeeByCondition(Employee employee); --> <select id="selectEmployeeByCondition" resultType="employee">select emp_id,emp_name,emp_salary from t_emp<!-- where標簽會自動去掉“標簽體內前面多余的and/or” --><where><!-- 使用if標簽,讓我們可以有選擇的加入SQL語句的片段。這個SQL語句片段是否要加入整個SQL語句,就看if標簽判斷的結果是否為true --><!-- 在if標簽的test屬性中,可以訪問實體類的屬性,不可以訪問數據庫表的字段 --><if test="empName != null"><!-- 在if標簽內部,需要訪問接口的參數時還是正常寫#{} -->or emp_name=#{empName}</if><if test="empSalary > 2000">or emp_salary>#{empSalary}</if><!--第一種情況:所有條件都滿足 WHERE emp_name=? or emp_salary>?第二種情況:部分條件滿足 WHERE emp_salary>?第三種情況:所有條件都不滿足 沒有where子句--></where> </select>
3. set標簽
<!-- void updateEmployeeDynamic(Employee employee) --> <update id="updateEmployeeDynamic">update t_emp<!-- set emp_name=#{empName},emp_salary=#{empSalary} --><!-- 使用set標簽動態管理set子句,并且動態去掉兩端多余的逗號 --><set><if test="empName != null">emp_name=#{empName},</if><if test="empSalary < 3000">emp_salary=#{empSalary},</if></set>where emp_id=#{empId}<!--第一種情況:所有條件都滿足 SET emp_name=?, emp_salary=?第二種情況:部分條件滿足 SET emp_salary=?第三種情況:所有條件都不滿足 update t_emp where emp_id=?沒有set子句的update語句會導致SQL語法錯誤--> </update>
4. trim標簽(了解)
使用trim標簽控制條件部分兩端是否包含某些字符
-
prefix屬性:指定要動態添加的前綴
-
suffix屬性:指定要動態添加的后綴
-
prefixOverrides屬性:指定要動態去掉的前綴,使用“|”分隔有可能的多個值
-
suffixOverrides屬性:指定要動態去掉的后綴,使用“|”分隔有可能的多個值
<!-- List<Employee> selectEmployeeByConditionByTrim(Employee employee) --> <select id="selectEmployeeByConditionByTrim" resultType="com.atguigu.mybatis.entity.Employee">select emp_id,emp_name,emp_age,emp_salary,emp_genderfrom t_emp<!-- prefix屬性指定要動態添加的前綴 --><!-- suffix屬性指定要動態添加的后綴 --><!-- prefixOverrides屬性指定要動態去掉的前綴,使用“|”分隔有可能的多個值 --><!-- suffixOverrides屬性指定要動態去掉的后綴,使用“|”分隔有可能的多個值 --><!-- 當前例子用where標簽實現更簡潔,但是trim標簽更靈活,可以用在任何有需要的地方 --><trim prefix="where" suffixOverrides="and|or"><if test="empName != null">emp_name=#{empName} and</if><if test="empSalary > 3000">emp_salary>#{empSalary} and</if><if test="empAge <= 20">emp_age=#{empAge} or</if><if test="empGender=='male'">emp_gender=#{empGender}</if></trim> </select>
5. choose/when/otherwise標簽
在多個分支條件中,僅執行一個。
-
從上到下依次執行條件判斷
-
遇到的第一個滿足條件的分支會被采納
-
被采納分支后面的分支都將不被考慮
-
如果所有的when分支都不滿足,那么就執行otherwise分支
<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) --> <select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">select emp_id,emp_name,emp_salary from t_empwhere<choose><when test="empName != null">emp_name=#{empName}</when><when test="empSalary < 3000">emp_salary < 3000</when><otherwise>1=1</otherwise></choose><!--第一種情況:第一個when滿足條件 where emp_name=?第二種情況:第二個when滿足條件 where emp_salary < 3000第三種情況:兩個when都不滿足 where 1=1 執行了otherwise--> </select>
6. foreach標簽
基本用法
用批量插入舉例
<!--collection屬性:要遍歷的集合item屬性:遍歷集合的過程中能得到每一個具體對象,在item屬性中設置一個名字,將來通過這個名字引用遍歷出來的對象separator屬性:指定當foreach標簽的標簽體重復拼接字符串時,各個標簽體字符串之間的分隔符open屬性:指定整個循環把字符串拼好后,字符串整體的前面要添加的字符串close屬性:指定整個循環把字符串拼好后,字符串整體的后面要添加的字符串index屬性:這里起一個名字,便于后面引用遍歷List集合,這里能夠得到List集合的索引值遍歷Map集合,這里能夠得到Map集合的key--> <foreach collection="empList" item="emp" separator="," open="values" index="myIndex"><!-- 在foreach標簽內部如果需要引用遍歷得到的具體的一個對象,需要使用item屬性聲明的名稱 -->(#{emp.empName},#{myIndex},#{emp.empSalary},#{emp.empGender}) </foreach>
批量更新時需要注意
上面批量插入的例子本質上是一條SQL語句,而實現批量更新則需要多條SQL語句拼起來,用分號分開。也就是一次性發送多條SQL語句讓數據庫執行。此時需要在數據庫連接信息的URL地址中設置:
atguigu.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true
對應的foreach標簽如下:
<!-- int updateEmployeeBatch(@Param("empList") List<Employee> empList) --> <update id="updateEmployeeBatch"><foreach collection="empList" item="emp" separator=";">update t_emp set emp_name=#{emp.empName} where emp_id=#{emp.empId}</foreach> </update>
關于foreach標簽的collection屬性
如果沒有給接口中List類型的參數使用@Param注解指定一個具體的名字,那么在collection屬性中默認可以使用collection或list來引用這個list集合。這一點可以通過異常信息看出來:
Parameter 'empList' not found. Available parameters are [arg0, collection, list]
在實際開發中,為了避免隱晦的表達造成一定的誤會,建議使用@Param注解明確聲明變量的名稱,然后在foreach標簽的collection屬性中按照@Param注解指定的名稱來引用傳入的參數。
7. sql片段
抽取重復的SQL片段
<!-- 使用sql標簽抽取重復出現的SQL片段 --> <sql id="mySelectSql">select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp </sql>
引用已抽取的SQL片段
<!-- 使用include標簽引用聲明的SQL片段 --> <include refid="mySelectSql"/>
六、MyBatis高級擴展
1. Mapper批量映射優化
-
需求
Mapper 配置文件很多時,在全局配置文件中一個一個注冊太麻煩,希望有一個辦法能夠一勞永逸。
-
配置方式
Mybatis 允許在指定 Mapper 映射文件時,只指定其所在的包:
<mappers><package name="com.atguigu.mapper"/> </mappers>
此時這個包下的所有 Mapper 配置文件將被自動加載、注冊,比較方便。
-
資源創建要求
-
Mapper 接口和 Mapper 配置文件名稱一致
-
Mapper 接口:EmployeeMapper.java
-
Mapper 配置文件:EmployeeMapper.xml
-
-
Mapper 配置文件放在 Mapper 接口所在的包內
-
可以將mapperxml文件放在mapper接口所在的包!
-
可以在sources下創建mapper接口包一致的文件夾結構存放mapperxml文件
-
2. 插件和分頁插件PageHelper
2.1 插件機制和PageHelper插件介紹
MyBatis 對插件進行了標準化的設計,并提供了一套可擴展的插件機制。插件可以在用于語句執行過程中進行攔截,并允許通過自定義處理程序來攔截和修改 SQL 語句、映射語句的結果等。
具體來說,MyBatis 的插件機制包括以下三個組件:
-
Interceptor
(攔截器):定義一個攔截方法intercept
,該方法在執行 SQL 語句、執行查詢、查詢結果的映射時會被調用。 -
Invocation
(調用):實際上是對被攔截的方法的封裝,封裝了Object target
、Method method
和Object[] args
這三個字段。 -
InterceptorChain
(攔截器鏈):對所有的攔截器進行管理,包括將所有的 Interceptor 鏈接成一條鏈,并在執行 SQL 語句時按順序調用。
插件的開發非常簡單,只需要實現 Interceptor 接口,并使用注解 @Intercepts
來標注需要攔截的對象和方法,然后在 MyBatis 的配置文件中添加插件即可。
PageHelper 是 MyBatis 中比較著名的分頁插件,它提供了多種分頁方式(例如 MySQL 和 Oracle 分頁方式),支持多種數據庫,并且使用非常簡單。下面就介紹一下 PageHelper 的使用方式。
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md#如何配置數據庫方言
2.2 PageHelper插件使用
-
pom.xml引入依賴
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.11</version> </dependency>
-
mybatis-config.xml配置分頁插件
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/></plugin> </plugins>
其中,
com.github.pagehelper.PageInterceptor
是 PageHelper 插件的名稱,dialect
屬性用于指定數據庫類型(支持多種數據庫) -
頁插件使用
在查詢方法中使用分頁:
@Test public void testTeacherRelationshipToMulti() {TeacherMapper teacherMapper = session.getMapper(TeacherMapper.class);PageHelper.startPage(1,2);// 查詢Customer對象同時將關聯的Order集合查詢出來List<Teacher> allTeachers = teacherMapper.findAllTeachers(); //PageInfo<Teacher> pageInfo = new PageInfo<>(allTeachers);System.out.println("pageInfo = " + pageInfo);long total = pageInfo.getTotal(); // 獲取總記錄數System.out.println("total = " + total);int pages = pageInfo.getPages(); // 獲取總頁數System.out.println("pages = " + pages);int pageNum = pageInfo.getPageNum(); // 獲取當前頁碼System.out.println("pageNum = " + pageNum);int pageSize = pageInfo.getPageSize(); // 獲取每頁顯示記錄數System.out.println("pageSize = " + pageSize);List<Teacher> teachers = pageInfo.getList(); //獲取查詢頁的數據集合System.out.println("teachers = " + teachers);teachers.forEach(System.out::println); }
3. 逆向工程和MybatisX插件
3.1 ORM思維介紹
ORM(Object-Relational Mapping,對象-關系映射)是一種將數據庫和面向對象編程語言中的對象之間進行轉換的技術。它將對象和關系數據庫的概念進行映射,通過一系列的操作將對象關聯到數據表中的一行或多行上。
讓我們可以使用面向對象思維進行數據庫操作!!!
ORM 框架通常有半自動和全自動兩種方式。
-
半自動 ORM 通常需要程序員手動編寫 SQL 語句或者配置文件,將實體類和數據表進行映射,還需要手動將查詢的結果集轉換成實體對象。
-
全自動 ORM 則是將實體類和數據表進行自動映射,使用 API 進行數據庫操作時,ORM 框架會自動執行 SQL 語句并將查詢結果轉換成實體對象,程序員無需再手動編寫 SQL 語句和轉換代碼。
下面是半自動和全自動 ORM 框架的區別:
-
映射方式:半自動 ORM 框架需要程序員手動指定實體類和數據表之間的映射關系,通常使用 XML 文件或注解方式來指定;全自動 ORM 框架則可以自動進行實體類和數據表的映射,無需手動干預。
-
查詢方式:半自動 ORM 框架通常需要程序員手動編寫 SQL 語句并將查詢結果集轉換成實體對象;全自動 ORM 框架可以自動組裝 SQL 語句、執行查詢操作,并將查詢結果轉換成實體對象。
-
性能:由于半自動 ORM 框架需要手動編寫 SQL 語句,因此程序員必須對 SQL 語句和數據庫的底層知識有一定的了解,才能編寫高效的 SQL 語句;而全自動 ORM 框架通過自動優化生成的 SQL 語句來提高性能,程序員無需進行優化。
-
學習成本:半自動 ORM 框架需要程序員手動編寫 SQL 語句和映射配置,要求程序員具備較高的數據庫和 SQL 知識;全自動 ORM 框架可以自動生成 SQL 語句和映射配置,程序員無需了解過多的數據庫和 SQL 知識。
常見的半自動 ORM 框架包括 MyBatis 等;常見的全自動 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。
3.2 逆向工程
MyBatis 的逆向工程是一種自動化生成持久層代碼和映射文件的工具,它可以根據數據庫表結構和設置的參數生成對應的實體類、Mapper.xml 文件、Mapper 接口等代碼文件,簡化了開發者手動生成的過程。逆向工程使開發者可以快速地構建起 DAO 層,并快速上手進行業務開發。 MyBatis 的逆向工程有兩種方式:通過 MyBatis Generator 插件實現和通過 Maven 插件實現。無論是哪種方式,逆向工程一般需要指定一些配置參數,例如數據庫連接 URL、用戶名、密碼、要生成的表名、生成的文件路徑等等。 總的來說,MyBatis 的逆向工程為程序員提供了一種方便快捷的方式,能夠快速地生成持久層代碼和映射文件,是半自動 ORM 思維像全自動發展的過程,提高程序員的開發效率。
注意:逆向工程只能生成單表crud的操作,多表查詢依然需要我們自己編寫!
3.3 逆向工程插件MyBatisX使用
MyBatisX 是一個 MyBatis 的代碼生成插件,可以通過簡單的配置和操作快速生成 MyBatis Mapper、pojo 類和 Mapper.xml 文件。下面是使用 MyBatisX 插件實現逆向工程的步驟:
-
安裝插件:
在 IntelliJ IDEA 中打開插件市場,搜索 MyBatisX 并安裝。
-
使用 IntelliJ IDEA連接數據庫
-
連接數據庫
-
填寫信息
-
展示庫表
-
逆向工程使用
-
-
查看生成結果
-
逆向工程案例使用
正常使用即可,自動生成單表的crud方法!
package com.atguigu.mapper;import com.atguigu.pojo.User;/** * @author Jackiechan * @description 針對表【user】的數據庫操作Mapper * @createDate 2023-06-02 16:55:32 * @Entity com.atguigu.pojo.User */ public interface UserMapper {int deleteByPrimaryKey(Long id);int insert(User record);int insertSelective(User record);User selectByPrimaryKey(Long id);int updateByPrimaryKeySelective(User record);int updateByPrimaryKey(User record);}
七、MyBatis總結
-
Mybatis環境所需依賴 ★
-
配置
-
Mybatis全局配置
-
Mapper配置 ★
-
-
Mapper接口 ★
-
API
-
SqlSessionFactory
-
SqlSession
-
-
逆向工程
-
分頁插件