MyBatis詳細介紹
技術標籤:java框架資料庫javaspringmysqlssm
1 簡介
1.1 什麼是Mybatis
- MyBatis 是一款優秀的持久層框架
- 它支援定製化 SQL、儲存過程以及高階對映。
- MyBatis 避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。
- MyBatis 可以使用簡單的 XML 或註解來配置和對映原生資訊,將介面和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java物件)對映成資料庫中的記錄。
- MyBatis 本是apache的一個開源專案iBatis, 2010年這個專案由apache software foundation 遷移到了[google code](https://baike.baidu.com/item/google code/2346604),並且改名為MyBatis 。
- 2013年11月遷移到Github。
獲取Mybatis
-
maven倉庫
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
-
github : https://github.com/mybatis/mybatis-3/releases
-
中文文件:https://mybatis.org/mybatis-3/zh/index.html
1.2 特點
- 簡單易學
- 本身就很小且簡單。
- 沒有任何第三方依賴,最簡單安裝只要兩個jar檔案+配置幾個sql對映檔案易於學習
- 易於使用,通過文件和原始碼,可以比較完全的掌握它的設計思路和實現。
- 靈活:
- mybatis不會對應用程式或者資料庫的現有設計強加任何影響。
- sql寫在xml裡,便於統一管理和優化。通過sql語句可以滿足操作資料庫的所有需求。
- 鬆耦合
- 解除sql與程式程式碼的耦合
- 通過提供DAO層,將業務邏輯和資料訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。
- sql和程式碼的分離,提高了可維護性。
- 提供標籤
- 提供對映標籤,支援物件與資料庫的orm欄位關係對映
- 提供物件關係對映標籤,支援物件關係組建維護
- 提供xml標籤,支援編寫動態sql。
1.3 持久層
持久化:將資料從瞬時狀態轉化為持久狀態。
之所以有持久層,就是將資料能斷電後儲存。一些其他原因,比如記憶體太貴
Dao層就是一個持久層,層的概念是為了解耦合。
MyBatis 是一款優秀的持久層框架
1.4 為什麼使用Mybatis
- 非常方便將資料庫存入到資料庫中
- 框架,簡化JDBC,自動化
- 不使用Mybatis也能持久化,但是它更容易、更方便上手
- 優點:簡單易學、靈活、sql與程式碼分離、可維護性、標籤
- 最重要的一點:使用的人多
2 第一個Mybatis程式
pojos
package com.zl.pojos;
//對應資料庫的一張表結構
public class Students {
private int id;
private int age;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
...
}
DAO介面
public interface StudentsMapper {
public List<Students> getStudents();
}
對映檔案
<?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">
<!--namespace對應一個mapper介面-->
<mapper namespace="com.zl.dao.StudentsMapper">
<select id="getStudents" resultType="com.zl.pojos.Students">
select * from students
</select>
</mapper>
核心配置檔案
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--環境,可以配置多個-->
<environments default="development">
<environment id="development">
<!--事務型別-->
<transactionManager type="JDBC"/>
<!--type:執行緒池-->
<dataSource type="POOLED">
<!--四個配置項-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--繫結對映檔案-->
<mapper resource="com/zl/dao/StudentsMapper.xml"/>
</mappers>
</configuration>
工具類
public class Mybatis_utils {
//SqlSessionFactory建立單例,可以重複使用,建立SqlSession
private static SqlSessionFactory sqlSessionFactory;
static{
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//SqlSessionFactoryBuilder在建立sqlSessionFactory即可刪除
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
測試
public class Test {
@org.junit.Test
public void test1(){
//SqlSession不是執行緒安全的,每次使用完之後應該關閉
try(SqlSession session = Mybatis_utils.getSqlSession()){
StudentsMapper students = session.getMapper(StudentsMapper.class);
List<Students> list = students.getStudents();
for(Students s: list){
System.out.println(s);
System.out.println("");
}
}
}
}
3 CRUD
更新
<insert id="addStudents" parameterType="com.zl.pojos.Students" >
insert into students (`name`,`age`) values (#{name}, #{age})
</insert>
<update id="updateStudents" parameterType="com.zl.pojos.Students">
update students set `name` = #{name},`age` = #{age} where `id` = #{id}
</update>
<delete id="deleteStudents" parameterType="com.zl.pojos.Students">
delete from `students` where `id` = #{id}
</delete>
測試
public void updateTest(){
try(SqlSession session = Mybatis_utils.getSqlSession()){
StudentsMapper mapper = session.getMapper(StudentsMapper.class);
Students students = new Students(0, 12, "Jane");
int line = mapper.addStudents(students);
System.out.println("line:" + line);
students.setId(1);
line = mapper.updateStudents(students);
System.out.println("line:" + line);
students.setId(2);
line = mapper.deleteStudents(students);
System.out.println("line:" + line);
session.commit();
List<Students> list = mapper.getStudents();
for(Students s: list){
System.out.println(s);
System.out.println("");
}
}
}
parameterType擴充套件
- map 使用鍵值對傳參
- 單個傳參查詢,無需指定parameterType
- 模糊查詢
<select id="getStudentsById" resultType="com.zl.pojos.Students">
select * from `students` where `id` = #{id}
</select>
<select id="getStudentsByName" parameterType="map" resultType="com.zl.pojos.Students">
select * from `students` where `name` = #{sname}
</select>
<select id="getStudentsLikeName" resultType="com.zl.pojos.Students">
select * from `students` where `name` like #{name}
</select>
測試
try(SqlSession session = Mybatis_utils.getSqlSession()){
StudentsMapper students = session.getMapper(StudentsMapper.class);
List<Students> list = students.getStudentsById(3);
showList(list);
Map<String, Object> map = new HashMap<>();
map.put("sname", "Tom"); //通過鍵值對,可以多個
list = students.getStudentsByName(map);
showList(list);
list = students.getStudentsLikeName("_o%");
showList(list);
}
4 配置解析
研究核心配置檔案中的屬性配置
4.1 環境配置(environments)
MyBatis 可以配置成適應多種環境,比如開發環境、上線環境、測試環境等。
儘管可以配置多個環境,但每個 SqlSessionFactory 例項只能選擇一種環境。
<!--default選擇預設環境-->
<environments default="development">
事務管理器(transactionManager)
<transactionManager type=""[JDBC|MANAGED]"">
-
JDBC – 這個配置直接使用了 JDBC 的提交和回滾設施,它依賴從資料來源獲得的連線來管理事務作用域,常用
-
MANAGED - 讓容器來管理事務的整個生命週期(比如 JEE 應用伺服器的上下文)
如果使用 Spring + MyBatis,則沒有必要配置事務管理器,因為 Spring 模組會使用自帶的管理器來覆蓋前面的配置。
資料來源(dataSource)
<dataSource type="[UNPOOLED|POOLED|JNDI]">
- UNPOOLED– 這個資料來源的實現會每次請求時開啟和關閉連線。
- POOLED– 常用型別選擇,這種資料來源的實現利用“池”的概念將 JDBC 連線物件組織起來,避免了建立新的連線例項時所必需的初始化和認證時間。
- JNDI – 這個資料來源實現是為了能在如 EJB 或應用伺服器這類容器中使用。在EJB用,目前很少用
4.2 屬性(properties)
這些屬性可以在外部進行配置,並可以進行動態替換。
<properties resource="org/mybatis/example/config.properties">
<!--以下的property也可以不寫,會自動載入配置檔案中的內容-->
<property name="username" value="root"/>
<property name="password" value="123456"/>
</properties>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
配置檔案
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_test?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
注意點:核心配置中,各個配置有嚴格的順序
The content of element type "configuration" must match "(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".
4.3 設定(settings)
這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的執行時行為。
常用的setting配置
<settings>
<!--全域性性地開啟或關閉所有對映器配置檔案中已配置的任何快取-->
<setting name="cacheEnabled" value="true"/>
<!--延遲載入的全域性開關。當開啟時,所有關聯物件都會延遲載入-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--允許 JDBC 支援自動生成主鍵,需要資料庫驅動支援-->
<setting name="useGeneratedKeys" value="false"/>
<!--是否開啟駝峰命名自動對映,即從經典資料庫列名 A_COLUMN 對映到經典 Java 屬性名 aColumn-->
<setting name="mapUnderscoreToCamelCase" value="false"/>
<!--指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢-->
<setting name="logImpl" value="JDK_LOGGING "/>
</settings>
4.4 類型別名(typeAliases)
類型別名可為 Java 型別設定一個縮寫名字。 它僅用於 XML 配置,意在降低冗餘的全限定類名書寫。
<typeAliases>
<package name="com.zl.pojos"/>
</typeAliases>
<typeAliases>
<typeAlias type="com.zl.pojos.Students" alias="stu"/>
</typeAliases>
有兩種方式:
- 指定包名,使用時直接使用類名,Mybatis會在包下搜尋需要的Jave Bean
- 直接為單一型別增加別名,使用時使用別名即可
<!--已指定包名,直接使用students,首字母也可以不用大寫-->
<select id="getStudents" resultType="students">
select * from students
</select>
<!--使用stu-->
<select id="getStudents" resultType="stu">
select * from students
</select>
4.5 mappers(對映器)
作用:告訴Mybatis到哪裡去找尋SQL語句。
方式一:
<!-- 使用相對於類路徑的資源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper resource="org/mybatis/builder/BlogMapper.xml"/>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
方式二:因為是指定類名,所以資源名要與類名同名、且在同一目錄下。
<!-- 使用對映器介面實現類的完全限定類名 -->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
<mapper class="org.mybatis.builder.BlogMapper"/>
<mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
方式三:同方式二,因為是指定類名,所以資源名要與類名同名、且在同一目錄下。
<!-- 將包內的對映器介面實現全部註冊為對映器 -->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
4.6 其他配置
4.7 生命週期和作用域
不同作用域和生命週期類別是至關重要的,因為錯誤的使用會導致非常嚴重的併發問題。
-
SqlSessionFactoryBuilder
- 一旦建立了 SqlSessionFactory,就不再需要它了
- 最佳作用域是方法作用域
-
SqlSessionFactory
- SqlSessionFactory 一旦被建立就應該在應用的執行期間一直存在
- 沒有任何理由丟棄它或重新建立另一個例項
- 它維護一個連線池,供SqlSession獲取連線
-
SqlSession
- 每個執行緒都應該有它自己的 SqlSession 例項
- SqlSession 的例項不是執行緒安全的,因此是不能被共享的
- 最佳的作用域是請求或方法作用域,即使用完就關閉
4.8 結果對映
結果集可以使用一個map接收,也可以使用JavaBean或者POJO接收,之前的例子都是使用POJO接收。
使用map接收:
<select id="getStudents" resultType="map">
select id, username, hashedPassword from students
</select>
使用Pojo接收:
<select id="getStudents" resultMap="students">
select * from students
</select>
問題:使用Pojo接收時,資料庫列名與物件類的屬性名不一致怎麼辦?如下 “name”與“sName”
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xBVqw4pF-1613116023827)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208160905129.png)][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-8J0m73ly-1613116023829)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208160927986.png)]
解決:使用結果對映
<!--建立一個resultMap結果對映-->
<resultMap id="myMap" type="students">
<!--colume代表資料庫列名,property代表要對映到的類的屬性名-->
<result property="sName" column="name"/>
<!--未明確標出的對映,會自動安裝列名與屬性名相同對映-->
</resultMap>
<select id="getStudents" resultMap="myMap">
select * from students
</select>
結論:
resultMap
元素是 MyBatis 中最重要最強大的元素。- 它可以讓你從 90% 的 JDBC
ResultSets
資料提取程式碼中解放出來 - ResultMap 的設計思想是,對簡單的語句做到零配置,對於複雜一點的語句,只需要描述語句之間的關係就行了。
- 如果這個世界總是這麼簡單就好了。對於複雜的SQL語句,就沒那麼簡單了,在後面繼續介紹。
5 日誌
5.1 日誌工廠STDOUT_LOGGING
在核心配置檔案中配置setting即可,無需外載入jar包
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-1ZpbesY3-1613116023833)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208163732473.png)]
5.2 log4j
log4J是java常用日誌工具,全稱是log for java
介紹:
- 通過使用Log4j,我們可以控制日誌資訊輸送的目的地是控制檯、檔案、GUI元件,甚至是套介面伺服器等
- 可以控制每一條日誌的輸出格式
- 通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程。
- 最重要的是,可以通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
配置:
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
需要引入jar包,maven引入
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
控制日誌的配置檔案
#將等級為DEBUG的日誌資訊輸出到console和file這兩個目的地,console和file的定義在下面的程式碼
log4j.rootLogger=DEBUG,console,file
#控制檯輸出的相關設定
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#檔案輸出的相關設定
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/zl.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日誌輸出級別
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
MyBatis使用輸出示例:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Zl2jrHvV-1613116023835)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208170633107.png)]
自用使用示例:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-zB3ZyCnO-1613116023837)(C:\Users\zl\AppData\Roaming\Typora\typora-user-images\image-20210208171225481.png)]
6 分頁
為什麼使用分頁
- 分頁有利於提取少量資料,提高響應速度
- 減少系統負擔
6.1 使用Limit分頁
資料庫分頁的本質都是包含limit的SQL語句分頁。
mybatis分頁示例:
<select id="getStudentsLimit" parameterType="map" resultType="students">
select * from `students` limit #{start},#{size}
</select>
測試:
@org.junit.Test
public void testPage(){
try (SqlSession session = Mybatis_utils.getSqlSession()){
StudentsMapper mapper = session.getMapper(StudentsMapper.class);
int start = 0;
int size = 4;
int page = 1;
Map<String,Integer> map = new HashMap<>();
map.put("start", start);
map.put("size",size);
List<Students> list = mapper.getStudentsLimit(map);
while(list.size() > 0) {
showList(list);
System.out.println(page + " page");
start = page * size;
page++;
map.put("start", start);
list = mapper.getStudentsLimit(map);
}
}
}
注意點:如果使用傳兩個引數(基本型別和String)的方法,需要使用@Param註解,引數是列名。
import org.apache.ibatis.annotations.Param;
selectAlbum(@Param("userid") Integer userid, @Param("albumName") String albumName);
6.2 RowBonds分頁
以前的程式碼使用的比較多,目前較少使用,看到老程式碼不慌!
6.3 分頁外掛
比較流行的PageHelper
7 使用註解
提一句:面向介面程式設計,介面是定義一系列的行為,是物件與物件互動的協議,面向介面程式設計最大的好處就是解耦合,從而帶來可擴充套件性、可維護性、分層開發、分模組開發等。
7.1 註解示例
首先,在方法上定義sql語句的註解
public interface UserMapper {
@Select("select * from user")
List<User> getUsers();
}
因為沒有xml檔案了,所以在核心配置檔案中,不能通過對映mapper檔案來繫結,可以通過之前介紹的後兩種方法:指定類名和指定包
<mappers>
<!--指定類名-->
<mapper class="com.zl.dao.UserMapper"/>
</mappers>
測試
@Test
public void annotationTest(){
try(SqlSession session = Mybatis_utils.getSqlSession()){
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
}
}
**特別說明:**使用註解來對映簡單語句會使程式碼顯得更加簡潔,但對於稍微複雜一點的語句,Java 註解不僅力不從心,還會讓你本就複雜的 SQL 語句更加混亂不堪。 因此,如果你需要做一些很複雜的操作,最好用 XML 來對映語句。
7.2 本質
- 反射
- 動態代理
7.3 流程解析
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-nIUtvB2w-1613116023838)(C:\Users\zl\Downloads\1.png)]
7.4 使用註解CRUD
@Select("select * from user")
List<User> getUsers();
@Insert("insert into `user` (`name`,`pwd`) values(#{name}, #{pwd})")
int addUsers(User user);
@Update("update `user` set `name`=#{name} where `id` = #{id}")
int updateUserName(@Param("id") int id, @Param("name") String name);
@Delete("delete from `user` where `id` = #{id}")
int deleteUser(@Param("id") int id);
測試方法和用xml對映是一樣的。
#{} ${}的區別:
- #{}是預編譯,${}是字元拼接
- 使用#{}防止SQL注入,提高系統的安全性。
8 Lombok
Lombok是一種 Java實用工具,可用來幫助開發人員消除Java的冗長,尤其是對於簡單的Java物件(POJO), 它通過註釋實現這一目的。
安裝步驟:
- IDEA中下載外掛
- 匯入Jar包(maven倉庫)
Lombok註解
- @Data:註解在類上,將類提供的所有屬性都新增get、set方法,並新增、equals、canEquals、hashCode、toString方法
- @Setter:註解在類上,為所有屬性新增set方法、註解在屬性上為該屬性提供set方法
- @Getter:註解在類上,為所有的屬性新增get方法、註解在屬性上為該屬性提供get方法
- @NotNull:在引數中使用時,如果呼叫時傳了null值,就會丟擲空指標異常
- @Synchronized 用於方法,可以鎖定指定的物件,如果不指定,則預設建立一個物件鎖定
- @Log作用於類,建立一個log屬性
- @Builder:使用builder模式建立物件
- @NoArgsConstructor:建立一個無參建構函式
- @AllArgsConstructor:建立一個全參建構函式
- @ToString:建立一個toString方法
- @Accessors(chain = true)使用鏈式設定屬性,set方法返回的是this物件。
- @RequiredArgsConstructor:建立物件, 例: 在class上新增@RequiredArgsConstructor(staticName = “of”)會建立生成一個靜態方法
- @UtilityClass:工具類
- @ExtensionMethod:設定父類
- @FieldDefaults:設定屬性的使用範圍,如private、public等,也可以設定屬性是否被final修飾。
- @Cleanup: 關閉流、連線點。
- @EqualsAndHashCode:重寫equals和hashcode方法。
- @Cleanup: 用於流等可以不需要關閉使用流物件.
使用:
import lombok.Data;
//將類提供的所有屬性都新增get、set方法,並新增、equals、canEquals、hashCode、toString方法
@Data
public class User {
private int id;
private String name;
private String pwd;
private String phone;
}
Lombok的爭議:
Lombok為java程式碼的精簡提供了一種方式,但是**所有的原始碼很多時候是用來閱讀的,只有很少的時間是用來執行的。**Lombok會降低程式碼的可讀性,而且對於升級和擴充套件帶來了一定潛在難度。
9 關聯查詢
當一個表需要同時關聯另一個表查詢時,需要用到association關鍵字。
比如,查詢一個學生表,附帶其中個的老師資訊,學生表的屬性是(id,name,tid),tid指向老師表的id。
create table students
(
id int auto_increment comment 'ID' primary key,
name varchar(100) not null comment '姓名',
tid int not null
)
create table teacher
(
id int auto_increment primary key,
name varchar(20) not null
)
學生表的POJO
import lombok.Data;
@Data
public class Student {
private int id;
private String name;
private Teacher teacher; //Teacher類
}
有兩種方式查詢,一種是連線查詢,一種是子查詢
<!--連線查詢-->
<resultMap id="sMap" type="student">
<!--property是student類屬性名,column代表查詢出的表的列名-->
<result column="sname" property="name"/>
<result column="sid" property="id"/>
<!--javaType需要對映到的類-->
<association property="teacher" javaType="Teacher">
<!--這裡表示的是JavaType的對映-->
<result column="tid" property="id"/>
<result column="tname" property="name"/>
</association>
</resultMap>
<select id="getStudents2" resultMap="sMap">
select s.id sid, s.name sname, t.id tid, t.name tname
from `students` as s
left join `teacher` as t
on s.tid = t.id
</select>
<!--子查詢-->
<resultMap id="stMap" type="student">
<result column="id" property="id"/>
<result column="name" property="name"/>
<!--select代表子查詢,查詢的結果對映到javaType中,column代表傳遞給子查詢的引數-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacherById"/>
</resultMap>
<select id="getStudents" resultMap="stMap">
select * from `students`
</select>
<select id="getTeacherById" resultType="Teacher">
select * from `teacher` where `id` = #{id}
</select>
結果:
Student(id=3, name=李四, teacher=Teacher(id=1, name=Janey))
Student(id=5, name=Tom, teacher=Teacher(id=2, name=wen))
Student(id=24, name=zhangsan, teacher=Teacher(id=1, name=Janey))
Student(id=25, name=lisi, teacher=Teacher(id=2, name=wen))
10 集合查詢
當一個pojo型別中有一個集合時,需要通過collection關鍵字查詢。
比如,老師表中有一個學生集合
import lombok.Data;
import java.util.List;
@Data
public class Teacher {
private int id;
private String name;
private List<Student> studentList; //學生集合
}
同樣,有兩種查詢方式,一種是連線查詢,一種是子查詢。相對來說,連線查詢時推薦的方式,更接近SQL查詢的思想,更易於理解和排除錯誤。
<!--連線查詢-->
<resultMap id="map1" type="Teacher">
<!--id與result一樣,只是id用於標記出ID欄位,有利於效能提高-->
<id property="id" column="tid"/>
<result property="name" column="tname"/>
<!--ofType代表集合中的元素型別-->
<collection property="studentList" ofType="student">
<!--這裡表示元素中的每一個物件-->
<id property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
<select id="getTeachers" resultMap="map1">
select t.id tid, t.name tname, s.id sid, s.name sname
from `teacher` as t
left join `students` as s
on t.id = s.tid
</select>
<!--子查詢-->
<resultMap id="map2" type="teacher">
<!--通過select查詢獲取javaType型別,傳參是column-->
<collection property="studentList" ofType="student" javaType="ArrayList" select="getSudentsbyTid" column="id"/>
</resultMap>
<select id="getTeachers2" resultMap="map2">
select * from `teacher`
</select>
<select id="getSudentsbyTid" resultType="student">
select * from `students` where `tid` = #{tid}
</select>
建議: 最好逐步建立結果對映。從最簡單的形態開始,逐步迭代。每次都使用單元測試保證每次迭代的正確性。
資料庫面試高頻:
- MySQL引擎
- InnoDB底層
- 索引
- 索引優化
11 動態SQL
動態 SQL 是 MyBatis 的強大特性之一。
動態SQL就是根據不同條件拼接SQL語句,MyBatis提供一套標籤在xml對映檔案中使用的條件判斷。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
11.1 if語句
使用動態 SQL 最常見情景是根據條件包含 where 子句的一部分。
<!--parameterType傳遞一個條件類-->
<select id="getUsers" parameterType="UserCondition" resultType="user">
select * from `user`
<!--were標籤會根據需要去掉and或者or,或者去掉where-->
<where>
<if test="name != null">
and `name` = #{name}
</if>
<if test="hasPhone == true">
and ( `phone` is not null and `phone` != "")
</if>
</where>
</select>
以上可以匹配getUser的兩個過載方法:
public interface UserMapper {
List<User> getUsers();
List<User> getUsers(UserCondition user);
}
11.2 choose
choose類似於java中的switch語句,只有一個case能夠執行,配合when、otherwise標籤,每個when相當於一個case,otherwise相當於default。
<select id="getUsers2" parameterType="UserCondition" resultType="User">
select * from `user`
<choose>
<when test="name != null">
where `name` = #{name}
</when>
<when test="hasPhone == true">
where ( `phone` is not null and `phone` != "")
</when>
<otherwise>
</otherwise>
</choose>
</select>
11.3 trim
trim可以用於自定義元素的功能,如內建的where,如下所示(注意:此例中的空格是必須的)。
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
比如if例子中,最後拼接的是 select * from user
where and name
= ?,此處就會把“and ”去掉,如果有的話。
內建的set是如下設定,此處是在結尾刪除多餘的“,”
<trim prefix="SET" suffixOverrides=",">
...
</trim>
更新語句示例:
<update id="updateUser" parameterType="UserCondition">
update `user`
<set>
<if test="name != null">
`name` = #{name},
</if>
<if test="phone != null">
`phone` = #{phone}
</if>
</set>
where `id` = #{id}
</update>
上面也可以寫成:
<update id="updateUser" parameterType="UserCondition">
update `user`
<!--如果trim內部條件都不滿足,什麼都不返回。
整個trim以prefix開頭,以suffix結尾,中間拼接的部分的最後去掉“,”-->
<trim prefix="set" suffix=" where id = #{id} " suffixOverrides=",">
<if test="name != null"> `name` = #{name} , </if>
<if test="phone != null"> `phone` = #{phone} , </if>
</trim>
</update>
翻譯:如果trim內部條件都不滿足,什麼都不返回。如果存在滿足條件,整個trim以prefix開頭,以suffix結尾,中間拼接的部分的最後去掉“,”
11.4 SQL片段
可以通過sql標籤將sql程式碼片段提取出來,減少重複SQL語句,提高複用。
<sql id="if-name-phone">
<if test="name != null"> `name` = #{name} , </if>
<if test="phone != null"> `phone` = #{phone} , </if>
</sql>
<update id="updateUser" parameterType="UserCondition">
update `user`
<trim prefix="set" suffix=" where id = #{id} " suffixOverrides=",">
<!--直接引用sql片段-->
<include refid="if-name-phone"/>
</trim>
</update>
一般只將簡單的if語句提取出來,連表查詢部分,複用性不高,一般不提取,where、set標籤等一般也不提取。
11.5 foreach
foreach 元素的功能非常強大,它允許你指定一個集合,宣告可以在元素體內使用的集合項(item)和索引(index)變數。它也允許你指定開頭與結尾的字串以及集合項迭代之間的分隔符。
<!--拼接select * from `user` where id in (1, 2, 3, 4)-->
<select id="getUsersForeach" parameterType="ArrayList" resultType="User">
select * from `user`
<where>
<if test="ids.size() > 0">
<!--collection集合名、open代表拼接的開頭,separator為每一項之間的分隔符,close是結尾字元,item代表每一項的變數名-->
<foreach collection="ids" open="id in (" separator="," close=")" item="id">
#{id}
</foreach>
</if>
</where>
</select>
11.6 總結
動態SQL的本質就是拼接字串,字串最後會形成一個SQL語句,所以在寫動態SQL時,首先應該寫出完整的SQL語句,並在資料庫中執行成功後,再寫Mybatis的拼接語句。
12 快取
12.1 簡介
what: 什麼是快取?
- 存在快取中的資料
- 資料庫一般是存在讀取速度較慢的磁碟中,而將經常查詢的資料放在記憶體中,從而提高查詢效率,解決高併發系統的效能問題。
why:為什麼使用快取?
- 減少與資料庫的互動,提升訪問速度,提升系統效率。
when:什麼時候使用快取?
- 當經常查詢的資料經常保持不變時,應該使用快取
12.2 主從複製、讀寫分離
簡單的來說,就是有兩個或兩個以上的資料庫,其中一個當做主資料庫,其他當做從資料庫,主資料庫負責更新資料,從資料庫負責查詢,當主資料庫更新時,會更新到所有從資料庫。就實現了主從複製、讀寫分離的設計。
優點:
- 當其中一個數據庫出問題時,其他資料庫可以工作,保證資料不易丟失,且穩定工作
- 寫操作更耗時,將讀寫分離,更新操作不會影響到查詢操作。
12.3 Mybatis快取
MyBatis 內建了一個強大的事務性查詢快取機制,它可以非常方便地配置和定製。
MyBatis定義了兩級快取:會話快取和二級快取
- 預設情況下,只啟用了本地的會話快取,它僅僅對一個會話中的資料進行快取。(在一個SqlSession中快取,呼叫close即快取結束)
- 二級快取需要手動開啟和配置,在對映檔案中使用標籤,作用域是所在namespace內。
- 二級快取也可以擴充套件Cache介面定義
12.4 一級快取
- 一級快取是預設開啟的
- 作用域是獲取SQLSession到close關閉
- 期間的查詢會被快取
- 期間所有的更新操作(insert、delete、update)會清除快取
- 預設採用LRU(最近最少使用演算法)
- 採用讀/寫快取,即快取不是共享的,可以安全的被呼叫者修改。
通過Log4j日誌輸出,可以檢視到select語句只執行了一次,如果中間有更新操作,select會再次執行。
[com.zl.dao.UserMapper.getUsers]-==> Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters:
[com.zl.dao.UserMapper.getUsers]-<== Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
======================
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
======================
[com.zl.dao.UserMapper.updateUser]-==> Preparing: update `user` set `phone` = ? where id = ?
[com.zl.dao.UserMapper.updateUser]-==> Parameters: 5698(String), 4(Integer)
[com.zl.dao.UserMapper.updateUser]-<== Updates: 1
[com.zl.dao.UserMapper.getUsers]-==> Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters:
[com.zl.dao.UserMapper.getUsers]-<== Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=5698)
User(id=5, name=Sam, pwd=789, phone=5566)
12.5 二級快取
一級快取的作用域太小,誕生了二級快取,二級快取的作用域是基於namespace,只需要加入標籤即可,當然,在核心配置檔案中可以增加setting設定,顯視的表示開啟全域性快取,雖然預設是開啟的。
首先查詢儲存在一級快取中,當close關閉時,會將一級快取儲存到二級快取,使用了java物件序列化。在另一個連線查詢中,讀取快取時,會從一級快取和二級快取中讀取。
<mapper namespace="com.zl.dao.UserMapper">
<!--開啟二級快取,標籤內也可以進行詳細配置-->
<cache/>
<select id="getUsers" parameterType="UserCondition" resultType="user">
select * from `user`
<where>
<if test="name != null">
and `name` = #{name}
</if>
<if test="hasPhone == true">
and ( `phone` is not null and `phone` != "")
</if>
</where>
</select>
</mapper>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
注意:二級快取使用了序列化,所以類必須可以序列化
測試:
@Test
public void testCached(){
try(SqlSession session1 = Mybatis_utils.getSqlSession()){
UserMapper mapper = session1.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
System.out.println("=====================");
}
try(SqlSession session1 = Mybatis_utils.getSqlSession()){
UserMapper mapper = session1.getMapper(UserMapper.class);
List<User> users = mapper.getUsers();
for (User user : users) {
System.out.println(user);
}
System.out.println("=====================");
}
}
下面結果顯示,兩次獲取連線,只查詢了一次 select * from user
[com.zl.dao.UserMapper]-Cache Hit Ratio [com.zl.dao.UserMapper]: 0.0
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1912850431.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [[email protected]]
[com.zl.dao.UserMapper.getUsers]-==> Preparing: select * from `user`
[com.zl.dao.UserMapper.getUsers]-==> Parameters:
[com.zl.dao.UserMapper.getUsers]-<== Total: 3
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
=====================
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [[email protected]]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [[email protected]]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 1912850431 to pool.
[org.apache.ibatis.io.SerialFilterChecker]-As you are using functionality that deserializes object streams, it is recommended to define the JEP-290 serial filter. Please refer to https://docs.oracle.com/pls/topic/lookup?ctx=javase15&id=GUID-8296D8E8-2B93-4B9A-856E-0A65AF9B8C66
[com.zl.dao.UserMapper]-Cache Hit Ratio [com.zl.dao.UserMapper]: 0.5
User(id=1, name=Tom, pwd=123, phone=null)
User(id=4, name=Janey, pwd=456, phone=1234567890)
User(id=5, name=Sam, pwd=789, phone=5566)
=====================
12.6 快取原理
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-9pXoP1Xr-1613116023840)(C:\Users\zl\Downloads\未命名檔案.png)]
12.7 自定義快取
可以在引用自定義快取。
步驟就是在java中繼承cache類,然後設定在標籤中。
<cache type="com.domain.something.MyCustomCache"/>
自定義cache需要實現的cache介面,不過目前有大量開源的資料庫快取框架,你確定能做的比他們好,再自定義快取吧,-_-||
public interface Cache {
String getId();
int getSize();
void putObject(Object key, Object value);
Object getObject(Object key);
boolean hasKey(Object key);
Object removeObject(Object key);
void clear();
}
也可以引入開源的快取框架,如ehcache(需要導包,具體檢視網上)
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
不過,一般會使用redies資料庫做快取