1. MyBatis簡介
視訊連結【狂神說Java】Mybatis最新完整教程IDEA版通俗易懂_哔哩哔哩_bilibili
環境:
- JDK 1.8
- MySQL 5.7
- maven 3.6.1
- IDEA
什麼是 MyBatis ?
- MyBatis 是一款優秀的持久層架構。
- 它支援自定義 SQL、存儲過程以及進階映射。
- MyBatis 免除了幾乎所有的 JDBC 代碼以及設定參數和擷取結果集的工作。
- MyBatis 可以通過簡單的 XML 或注解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為資料庫中的記錄。
- MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名為MyBatis 。
- 2013年11月遷移到Github。
1.1 如何擷取 MyBatis
- maven 倉庫
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
- Github:Releases · mybatis/mybatis-3 (github.com)
- 官方文檔:mybatis – MyBatis 3 | 簡介
1.2 持久化
資料持久化
- 持久化就是将程式的資料在持久狀态和瞬時狀态轉換的過程。
- 記憶體:斷電即失
- 資料庫 (jdbc),io 檔案持久化
- 生活:冷藏,罐頭。
為什麼需要持久化?
- 有一些對象不能讓他丢掉。
- 記憶體太貴了。
1.3 持久層
Dao 層,Service 層,Controller 層 …
- 完成持久化工作的代碼塊
- 層界限十分明顯。
1.4 為什麼需要 MyBatis
- 幫助程式員将資料存入到資料庫種
- 友善
- 傳統的 JDBC 代碼太複雜了。簡化、架構、自動化。
- 不用 MyBatis 也可以,更容易上手。技術沒有高低之分
- 優點:
- 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝隻要兩個jar檔案+配置幾個sql映射檔案易于學習,易于使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實作。
- 靈活:mybatis不會對應用程式或者資料庫的現有設計強加任何影響。 sql寫在xml裡,便于統一管理和優化。通過sql語句可以滿足操作資料庫的所有需求。
- 解除sql與程式代碼的耦合:通過提供DAO層,将業務邏輯和資料通路邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。
- 提供映射标簽,支援對象與資料庫的orm字段關系映射
- 提供對象關系映射标簽,支援對象關系組建維護
- 提供xml标簽,支援編寫動态sql。
最重要的一點:時用的人多!
2. 第一個 MyBatis 程式
思路:搭建環境 -> 導入 MyBatis -> 編寫代碼 -> 測試
2.1 搭建資料庫
CREATE DATABASE `mybatis`;
USE `mybatis`;
CREATE TABLE `user`(
`id` INT(20) NOT NULL PRIMARY KEY,
`name` VARCHAR(30) DEFAULT NULL,
`pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
INSERT INTO `user`(`id`,`name`,`pwd`) VALUES
(1,'origami','123456'),
(2,'tt','123456'),
(3,'hyp','123456')
建立項目
- 建立一個普通的 maven 項目
- 删除 src 目錄
- 導入 maven 依賴
<!--父工程--> <groupId>com.tt</groupId> <artifactId>MyBatis-Stu</artifactId> <version>1.0.0</version> <!--導入依賴--> <dependencies> <!--mysql驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <!--mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies>
2.2 建立一個子產品
- 編寫 mybatis 核心配置檔案
<?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核心配置檔案--> <configuration> <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://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8"/> <property name="username" value="root"/> <property name="password" value="hyp"/> </dataSource> </environment> </environments> </configuration>
- 編寫 mybatis 工具類
// sqlSessionFactory --> sqlSession public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory; static { String resource = "mybatis-config.xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } // 既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的執行個體。 // SqlSession 提供了在資料庫執行 SQL 指令所需的所有方法。 public static SqlSession getSqlSession() { return sqlSessionFactory.openSession(); } }
2.3 編寫代碼
- 實作類
public class User { private int id; private String name; private String pwd; public User() { } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + '}'; } }
- Dao 接口
public interface UserDao { List<User> getUserList(); }
- 接口實作類由原來的 UserDaoImpl 轉變為一個 Mapper 配置檔案
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace 綁定一個對于的 Dao/Mapper 接口--> <mapper namespace="com.tt.dao.UserDao"> <!--select查詢語句--> <select id="getUserList" resultType="com.tt.pojo.User"> select * from mybatis.user </select> </mapper>
2.4 測試
注意點:
org.apache.ibatis.binding.BindingException: Type interface com.tt.dao.UserDao is not known to the MapperRegistry.
MapperRegistry是什麼?
核心配置檔案種注冊 mappers
- junit 測試
public class UserDaoTest { @Test public void test() { // 第一步:獲得 SqlSession對象 SqlSession sqlSession = MyBatisUtils.getSqlSession(); // 執行 SQL UserDao mapper = sqlSession.getMapper(UserDao.class); List<User> userList = mapper.getUserList(); for (User user : userList) { System.out.println(user); } sqlSession.close(); } }
可能遇到的問題:
- 配置檔案沒有注冊
- 綁定接口錯誤
- 方法名錯誤
- 傳回值類型不對
- maven 導出資源問題
- 中文注釋問題
3. CRUD
1. namespace
namespace 中的包名要和 Dao/mapper 中的包名一緻!
2. select
選擇,查詢語句:
- id:就是對于的 namespace 中的方法名
- resultType:sql 語句執行的傳回值
- parameterType:參數類型!
- 編寫接口
// 根據ID查詢使用者 User getUserById(int id);
- 編寫對應的 mapper 中的 sql 語句
<select id="getUserById" parameterType="int" resultType="com.tt.pojo.User"> select * from mybatis.user where id = #{0}; </select>
- 測試
@Test public void getUserById() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.getUserById(1); System.out.println(user); sqlSession.close(); }
3. insert
- 編寫接口
// insert 一個使用者 int addUser(User user);
- 編寫對應的 mapper 的 sql 語句
<insert id="addUser" parameterType="com.tt.pojo.User"> insert into mybatis.user (id, name, pwd) values (#{id}, #{name}, #{pwd}); </insert>
- 測試
@Test public void addUser() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.addUser(new User(23, "哈哈", "1233412")); if (i > 0) { System.out.println("插入成功"); } // 送出事務 sqlSession.commit(); sqlSession.close(); }
4. update
- 編寫接口
// 修改使用者 int updateUser(User user);
- 編寫對應的 mapper 的 sql 語句
<update id="updateUser" parameterType="com.tt.pojo.User"> update mybatis.user set name=#{name},pwd=#{pwd} where id = #{id}; </update>
- 測試
@Test public void updateUser() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.updateUser(new User(23, "哈123哈", "12312412312")); if (i > 0) { System.out.println("更新成功"); } sqlSession.commit(); sqlSession.close(); }
5. delete
- 編寫接口
// 删除使用者 int deleteUser(int id);
- 編寫對應的 mapper 的 sql 語句
<delete id="deleteUser" parameterType="int"> delete from mybatis.user where id = #{id}; </delete>
- 測試
@Test public void deleteUser() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); int i = mapper.deleteUser(23); if (i > 0) { System.out.println("删除成功"); } sqlSession.commit(); sqlSession.close(); }
注意點:
- 增删改需要送出事務!
6. 分析錯誤
- 标簽不要比對錯,insert 語句使用 insert 标簽。
- resource 綁定 mapper 需要使用路徑。
- 程式配置檔案必須符合規範
- NullPointerException,沒有注冊到資源。
- 輸出的 xml 檔案中存在中文亂碼問題。
- maven 資源沒有導出問題。
7. 萬能Map
假設,我們的實體類,或者資料庫中的表,字段或者參數過多,應當考慮使用Map!
// 萬能 map
User getUserById2(Map<String,Object> map);
<!--對象中的數學,可以直接取出來 傳遞 map 的 key-->
<insert id="addUser" parameterType="map">
insert into mybatis.user (id, name, pwd) values (#{userId}, #{userName}, #{passWord});
</insert>
@Test
public void addUser2() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
HashMap<String, Object> map = new HashMap<>();
map.put("userId", 5);
map.put("userName", "哈哈哈");
map.put("passWord", "1234124");
int i = mapper.addUser2(map);
System.out.println(i);
sqlSession.commit();
sqlSession.close();
}
Map 傳遞參數,直接在 sql 中取出 key 即可。 [ parameterType=“map” ]
對象傳遞參數時,直接在 sql 中取對象的屬性即可 [ parameterType=“Object” ]
隻有一個參數的情況下,可以直接在 sql 中取到!
多個參數用 Map,或者注解!
8. 思考題
模糊查詢
- java 代碼執行的時候,傳遞通配符 % %
- 在 SQL 拼接中使用通配符。
4. 配置解析
1. 核心配置檔案
- mybatis-config.xml
- mybatis 的核心配置檔案包含了會深深影響 mybatis 行為的設定和屬性資訊。
configuration(配置) properties(屬性) settings(設定) typeAliases(類型别名) typeHandlers(類型處理器) objectFactory(對象工廠) plugins(插件) environments(環境配置) environment(環境變量) transactionManager(事務管理器) dataSource(資料源) databaseIdProvider(資料庫廠商辨別) mappers(映射器)
2. 環境配置 (environments)
MyBatis 可以配置成适應多種環境
不過要記住:盡管可以配置多個環境,但每個 SqlSessionFactory 執行個體隻能選擇一種環境。
學會使用配置多套運作環境!
MyBatis 預設的事務管理器假設 JDBC,連接配接池:POOLED
3. 屬性(properties)
我們可以通過 properties 屬性來實作引用配置檔案
這些屬性可以在外部進行配置,并可以進行動态替換。你既可以在典型的 Java 屬性檔案中配置這些屬性,也可以在 properties 元素的子元素中設定。[ db.properties ]
編寫一個配置檔案
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=hyp
在核心配置檔案中引入
<!--引入外部配置檔案-->
<properties resource="db.properties">
<property name="username" value="root"/>
</properties>
- 可以引入外部檔案
- 可以在 properties 中增加一些屬性配置
- 如果兩個檔案有同一個字段,優先使用外部配置檔案的!
4.類型别名(typeAliases)
- 類型别名可為 Java 類型設定一個縮寫名字。 它僅用于 XML 配置。
- 意在降低備援的全限定類名書寫。
<!--可以給實體類取别名-->
<typeAliases>
<typeAlias type="com.tt.pojo.User" alias="User"/>
</typeAliases>
也可也指定一個包名,MyBatis 會在包名下搜尋需要的Java Bean 比如:
掃描實體類的包,它的預設别名就為這個類的類名,首字母小寫!
<typeAliases>
<package name="com.tt.pojo"/>
</typeAliases>
在實體類比較少的時候,使用第一種方式
如果實體類十分多,建議使用第二種。
第一種可以DIY别名,第二種不寫,如果非要改,需要在實體上增加注解
@Alias("user")
public class User { ... }
5. 設定 (settings)
設定名 | 描述 | 有效值 | 預設值 |
---|---|---|---|
cacheEnabled | 全局性地開啟或關閉所有映射器配置檔案中已配置的任何緩存。 | true | false | true |
lazyLoadingEnabled | 延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載。 特定關聯關系中可通過設定 屬性來覆寫該項的開關狀态。 | true | false | false |
aggressiveLazyLoading | 開啟時,任一方法的調用都會加載該對象的所有延遲加載屬性。 否則,每個延遲加載屬性會按需加載(參考 )。 | 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 |
autoMappingUnknownColumnBehavior | 指定發現自動映射目标未知列(或未知屬性類型)的行為。 : 不做任何反應 : 輸出警告日志( 的日志等級必須設定為 ) : 映射失敗 (抛出 ) | NONE, WARNING, FAILING | NONE |
defaultExecutorType | 配置預設的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。 | SIMPLE REUSE BATCH | SIMPLE |
defaultStatementTimeout | 設定逾時時間,它決定資料庫驅動等待資料庫響應的秒數。 | 任意正整數 | 未設定 (null) |
defaultFetchSize | 為驅動的結果集擷取數量(fetchSize)設定一個建議值。此參數隻可以在查詢設定中被覆寫。 | 任意正整數 | 未設定 (null) |
defaultResultSetType | 指定語句預設的滾動政策。(新增于 3.5.2) | FORWARD_ONLY | SCROLL_SENSITIVE | SCROLL_INSENSITIVE | DEFAULT(等同于未設定) | 未設定 (null) |
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 使用的預設 。(新增于 3.4.5) | 一個類型别名或全限定類名。 | org.apache.ibatis.type.EnumTypeHandler |
callSettersOnNulls | 指定當結果集中值為 null 的時候是否調用映射對象的 setter(map 對象時為 put)方法,這在依賴于 Map.keySet() 或 null 值進行初始化時比較有用。注意基本類型(int、boolean 等)是不能設定成 null 的。 | true | false | false |
returnInstanceForEmptyRow | 當傳回行的所有列都是空時,MyBatis預設傳回 。 當開啟這個設定時,MyBatis會傳回一個空執行個體。 請注意,它也适用于嵌套的結果集(如集合或關聯)。(新增于 3.4.2) | true | false | false |
logPrefix | 指定 MyBatis 增加到日志名稱的字首。 | 任何字元串 | 未設定 |
logImpl | 指定 MyBatis 所用日志的具體實作,未指定時将自動查找。 | SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING | 未設定 |
proxyFactory | 指定 Mybatis 建立可延遲加載對象所用到的代理工具。 | CGLIB | JAVASSIST | JAVASSIST (MyBatis 3.3 以上) |
vfsImpl | 指定 VFS 的實作 | 自定義 VFS 的實作的類全限定名,以逗号分隔。 | 未設定 |
useActualParamName | 允許使用方法簽名中的名稱作為語句參數名稱。 為了使用該特性,你的項目必須采用 Java 8 編譯,并且加上 選項。(新增于 3.4.1) | true | false | true |
configurationFactory | 指定一個提供 執行個體的類。 這個被傳回的 Configuration 執行個體用來加載被反序列化對象的延遲加載屬性值。 這個類必須包含一個簽名為 的方法。(新增于 3.2.3) | 一個類型别名或完全限定類名。 | 未設定 |
shrinkWhitespacesInSql | 從SQL中删除多餘的空格字元。請注意,這也會影響SQL中的文字字元串。 (新增于 3.5.5) | true | false | false |
defaultSqlProviderType | Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the (or ) attribute on sql provider annotation(e.g. ), when these attribute was omitted. | A type alias or fully qualified class name | Not set |
6. 其他設定
- typeAliases(類型别名)
- objectFactory(對象工廠)
- plugins 插件
- mybatis-generator-core
- mybatis-plus
- 通用 mapper
7. 映射器 (mappers)
MapperRegistry :注冊綁定我們的 Mapper 檔案:
方式一:
<mappers>
<mapper resource="com/tt/dao/UserMapper.xml"/>
</mappers>
方式二:使用 class 檔案綁定檔案
<mappers>
<mapper class="com.tt.dao.UserMapper"/>
</mappers>
注意點:
- 接口和他的 Mapper 配置檔案必須同名
- 接口和他的 Mapper 配置檔案必須在同一個包下!
方式三:使用掃描包進入注入指定
<mappers>
<package name="com.tt.dao"/>
</mappers>
注意點:
- 接口和他的 Mapper 配置檔案必須同名
- 接口和他的 Mapper 配置檔案必須在同一個包下!
5. 生命周期和作用域
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-iZ9RHBEP-1626154896879)(pic/生命周期和作用域.png)]
了解我們之前讨論過的不同作用域和生命周期類别是至關重要的,因為錯誤的使用會導緻非常嚴重的并發問題。
SqlSessionFactoryBuilder
這個類可以被執行個體化、使用和丢棄,一旦建立了 SqlSessionFactory,就不再需要它了。 是以 SqlSessionFactoryBuilder 執行個體的最佳作用域是方法作用域(也就是局部方法變量)。 你可以重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 執行個體,但最好還是不要一直保留着它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。 — 官方文檔
- 一旦建立 SqlSessionFactory,就不再需要管它了
- 局部變量
SqlSessionFactory
SqlSessionFactory 一旦被建立就應該在應用的運作期間一直存在,沒有任何理由丢棄它或重新建立另一個執行個體。 使用 SqlSessionFactory 的最佳實踐是在應用運作期間不要重複建立多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。是以 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜态單例模式。
- 說白了,可以想象為:資料庫連接配接池
- SqlSessionFactory 一旦被建立就應該在應用的運作期間一直存在,沒有任何理由丢棄它或重新建立另一個執行個體。
- 是以 SqlSessionFactory 的最佳作用域是應用作用域。
- 最簡單的就是使用單例模式或者靜态單例模式。
SqlSession
每個線程都應該有它自己的 SqlSession 執行個體。SqlSession 的執行個體不是線程安全的,是以是不能被共享的,是以它的最佳的作用域是請求或方法作用域。 絕對不能将 SqlSession 執行個體的引用放在一個類的靜态域,甚至一個類的執行個體變量也不行。 也絕不能将 SqlSession 執行個體的引用放在任何類型的托管作用域中,比如 Servlet 架構中的 HttpSession。 如果你現在正在使用一種 Web 架構,考慮将 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以打開一個 SqlSession,傳回一個響應後,就關閉它。 這個關閉操作很重要,為了確定每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。 下面的示例就是一個確定 SqlSession 關閉的标準模式:在所有代碼中都遵循這種使用模式,可以保證所有資料庫資源都能被正确地關閉。try (SqlSession session = sqlSessionFactory.openSession()) { // 你的應用邏輯代碼 }
- 連接配接到連接配接池的一個請求!
- SqlSession 的執行個體不是線程安全的,是以是不能被共享的,是以它的最佳的作用域是請求或方法作用域。
- 用完之後需要趕緊關閉,否則資源被占用。
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-qNzeJuK3-1626154721526)(pic/SqlSessionFactory.png)]
這裡面每一個 Mapper,就代表一個具體的業務!
5. 解決屬性名和字段名不一緻的問題
1. 問題
資料庫的字段
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-18OVeybR-1626154721528)(pic/mybatis-資料庫字段.png)]
建立一個項目,拷貝之前的,測試實體類字段不一緻的情況。
修改 User 屬性字段,使其和資料庫中字段名不一緻
public class User {
private int id;
private String name;
private String password;
...
}
測試時,出現問題
User{id=1, name='origami', pwd='null'}
// select * from mybatis.user where id = #{id}
// 類型處理器
// select id,name,pwd from mybatis.user where id = #{id}
解決方法:
- 起别名
<select id="getUserById" parameterType="int" resultType="User"> select id, name, pwd as password from mybatis.user where id = #{0} </select>
2. resultMap
元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBC
resultMap
資料提取代碼中解放出來,并在一些情形下允許你進行一些 JDBC 不支援的操作。實際上,在為一些比如連接配接的複雜語句編寫映射代碼的時候,一份
ResultSets
能夠代替實作同等功能的數千行代碼。ResultMap 的設計思想是,對簡單的語句做到零配置,對于複雜一點的語句,隻需要描述語句之間的關系就行了。
resultMap
結果集映射
id name pwd
id name password
<!--結果集映射-->
<resultMap id="UserMap" type="User">
<!--column資料庫中的字段,property實體類中的屬性 簡單來說就是讓 pwd 映射為 password -->
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="getUserById" resultMap="UserMap"> <!-- resultMap! -->
select *
from mybatis.user
where id = #{id}
</select>
-
元素是 MyBatis 中最重要最強大的元素。resultMap
- ResultMap 的設計思想是,對簡單的語句做到零配置,對于複雜一點的語句,隻需要描述語句之間的關系就行了。
-
的優秀之處——你完全可以不用顯式地配置它們ResultMap
6. 日志
6.1 日志工廠
如果一個資料庫操作,出現異常,我們需要排錯。日志就是最好的助手。
曾經:sout、debug
現在:日志工廠
設定名 | 描述 | 有效值 | 預設值 |
---|---|---|---|
logImpl | 指定 MyBatis 所用日志的具體實作,未指定時将自動查找。 | SLF4J |LOG4J |LOG4J2 |JDK_LOGGING |COMMONS_LOGGING |STDOUT_LOGGING |NO_LOGGING | 未設定 |
- SLF4J
- LOG4J 【掌握】
- LOG4J2
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING 【掌握】
- NO_LOGGING
在 MyBatis 中具體使用哪一個日志實作,在設定中設定!
STDOUT_LOGGING标準日志輸出
在mybatis核心配置檔案中,配置日志。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
Opening JDBC Connection
Created connection 1765250898.
Setting autocommit to false on JDBC Connection [[email protected]]
==> Preparing: select * from mybatis.user where id = ?
==> Parameters: 1(Integer)
<== Columns: id, name, pwd
<== Row: 1, origami, 123456
<== Total: 1
User{id=1, name='origami', pwd='123456'}
Resetting autocommit to true on JDBC Connection [[email protected]]
Closing JDBC Connection [com.mysql.jdbc.JD[email protected]]
Returned connection 1765250898 to pool.
6.2 Log4j
什麼是Log4j?
- Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日志資訊輸送的目的地是控制台、檔案、GUI元件,甚至是套接口伺服器、NT的事件記錄器、UNIX Syslog守護程序等。
- 我們也可以控制每一條日志的輸出格式
- 通過一個配置檔案來靈活地進行配置,而不需要修改應用的代碼。
- 先導入 log4j 的包
<dependencies> <!-- https://mvnrepository.com/artifact/log4j/log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
- log4j.properties
#将等級為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/tt.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
- 配置 log4j 為日志的實作
<settings> <setting name="logImpl" value="LOG4J"/> </settings>
- log4j 的使用,執行測試運作剛才的查詢
Connected to the target VM, address: '127.0.0.1:64439', transport: 'socket' [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. [org.apache.ibatis.logging.LogFactory]-Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.datasource.pooled.PooledDataSource]-PooledDataSource forcefully closed/removed all connections. [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection [org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 1062186835. [org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [[email protected]] [com.tt.dao.UserMapper.getUserById]-==> Preparing: select * from mybatis.user where id = ? [com.tt.dao.UserMapper.getUserById]-==> Parameters: 1(Integer) [com.tt.dao.UserMapper.getUserById]-<== Total: 1 User{id=1, name='origami', pwd='123456'} [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 1062186835 to pool. Disconnected from the target VM, address: '127.0.0.1:64439', transport: 'socket'
簡單使用
- 在要使用 log4j 的類中,導入包 import org.apache.log4j.Logger;
- 日志對象,參數為目前類的 class
- 日志級别
logger.info("info:進入了testLog4j"); logger.debug("debug:進入了testLog4j"); logger.error("error:進入了testLog4j");
7. 分頁
為什麼要分頁?
- 減少資料的處理量
7.1 使用分頁
# 文法: SELECT * FROM user limit startIndex,pageSize;
SELECT * FROM `user` limit 3; # [0,n]
使用 Mybatis實作分頁,核心 SQL
- 接口
// 分頁 List<User> getUserByLimit(Map<String, Integer> map);
- Mapper.xml
<select id="getUserByLimit" parameterType="map" resultType="user"> select * from mybatis.user limit #{startIndex},#{pageSize} </select>
- 測試
@Test public void getUserByLimit() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); HashMap<String, Integer> map = new HashMap<>(); map.put("startIndex", 0); map.put("pageSize", 2); List<User> userList = mapper.getUserByLimit(map); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
7.2 RowsBounds 分頁
不再使用 SQL 實作分頁
- 接口
// 分頁2 List<User> getUserByRowBounds();
- mapper.xml
<!--分頁2--> <select id="getUserByRowBounds" resultMap="UserMap"> select * from mybatis.user </select>
- 測試代碼
@Test public void getUserByRowBounds() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); // RowBounds 實作 RowBounds rowBounds = new RowBounds(1, 2); // 通過 Java 代碼層面實作分頁 List<User> userList = sqlSession.selectList("com.tt.dao.UserMapper.getUserByRowBounds", null, rowBounds); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
不推薦使用,效率低。
7.3 分頁插件
Mybatis_PageHelper: Mybatis分頁插件 (gitee.com)
8. 使用注解開發
8.1 面向接口程式設計
- 大家之前都學過面向對象程式設計,也學習過接口,但在真正的開發中,很多時候我們會選擇面向接口程式設計。
- 根本原因:解耦,可擴充,提高複用,分層開發中,上層不用管具體的實作,大家遵守共同的标準,使得開發變得容易,規範性更好。
- 在一個面向對象的系統中,系統的各個功能是由區區多多的不同對象協作完成的。在這種情況下,各個對象内部是如何實作自己的。對系統設計人員來将就不那麼重要了。
- 而各個對象之間的協作關系則成為了系統設計的關鍵。小到不同類之間的通信,大到各子產品之間的互動,在系統設計之處都是要着重考慮的,這也是系統設計的主要工作内容。面向接口程式設計技術按照這種思想來程式設計的。
關于接口的了解
- 接口從更深層次的了解,應是定義(規範,限制)與實作(名實分離的原則)的分離。
- 接口的本身反應了系統設計人員對系統的抽象了解。
- 接口應有兩類:
- 第一類是對一個個體的抽象,它可以對應為一個抽象體(abstract class)
- 第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface)
- 一個個體可能有多個抽象面。抽象體與抽象面之間是有差別的。
三個面向的差別
- 面向對象是指,我們考慮問題時,以對象為機關,考慮它的屬性及方法。
- 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為機關,考慮它的實作
- 接口設計和非接口設計是針對複用技術而言的,與面向對象(過程)不是一個問題,更多的展現就是對系統整體的架構
8.2 使用注解開發
- 注解在接口上實作
@Select("select * from user") List<User> getUser();
- 需要在核心配置檔案綁定接口!
<!--綁定接口--> <mappers> <mapper class="com.tt.dao.UserMapper"/> </mappers>
- 測試
@Test public void test() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); List<User> userList = mapper.getUser(); for (User user : userList) { System.out.println(user); } sqlSession.close(); }
本質:反射機制實作
底層:動态代理模式!
mybatis 詳細執行流程
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bCvn9LFG-1626154721532)(pic/mybatis 詳細執行流程.png)]
8.3 注解實作 CRUD
我們可以在工具類建立的時候實作自動送出事務!
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession(true); // 參數設定為 true 自動送出
}
編寫接口,添加注解
public interface UserMapper {
@Select("select * from user")
List<User> getUser();
@Select("select * from user where id = #{id}")
User getUserByID(@Param("id") int id/*, @Param("name") String name*/);
@Insert("insert into user(id,name,pwd) value(#{id},#{name},#{password})")
int addUser(User user);
@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);
@Delete("delete from user where id=#{uid}")
int deleteUser(@Param("uid") int id);
}
測試
@Test
public void test() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUser();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
.... others
注意:我們必須要将接口綁定到核心配置檔案中!
關于 @Param() 注解
- 基本類型的參數或者 String 類型,需要加上
- 引用類型不需要加
- 如果隻有一個基本類型的話,可以忽略,但是建議都加上!
- 在 SQL 中引用的加上我們這裡的 @Param() 中設定的屬性名!
#{} 和 ${} 差別
- #是預編譯的方式,$是直接拼接;
- #不需要關注資料類型,mybatis實作自動資料類型轉換;$不做資料類型轉換,需要自行判斷資料類型;
- #可以防止sql注入;$不能防止sql注入;
- 如果隻有一個參數,預設情況下,#{}中可以寫任意的名字;${}中隻能用value來接收。
9. Lombox
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
- java library
- plugs
- bulid tools
- with one annotation your class
使用步驟:
- 在 IDEA 中安裝 Lombok 插件
- 在項目中導入 lombok 的 jar 包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> <scope>provided</scope> </dependency>
- 在實體類上加上注解即可
@Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; private String name; private String password; }
@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass
Lombok config system
Code inspections
Refactoring actions (lombok and delombok)
說明:
@Data:無參構造、get、set、toString、hashcode、equals
@AllArgsConstructor:全參構造
@NoArgsConstructor:無參構造
10. 多對一處理
- 多個學生對應一個老師
- 對于學生而言,關聯,多個學生關聯一個老師【多對一】
- 對于老師而言,集合,一個老師有很多學生【一對多】
實體類
@Data
public class Student {
private int id;
private String name;
// 學生需要關聯一個老師
private Teacher teacher;
}
@Data
public class Teacher {
private int id;
private String name;
}
測試環境搭建
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老師');
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, '小明', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, '小紅', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, '小張', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, '小李', 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, '小王', 1);
- 導入 lombok
- 建立實體類 Teacher,Student
- 建立 Mapper 接口
- 建立 Mapper.xml 檔案
- 在核心配置檔案中綁定注冊我們的 Mapper 接口或者檔案!
- 測試查詢是否能成功
按照查詢嵌套處理
<!-- 按照查詢嵌套處理
思路:
1. 查詢所有的學生
2. 根據查詢學生的 tid,尋找對應老師
-->
<select id="getStudent" resultMap="StudentTeacher">
select *
from student;
</select>
<resultMap id="StudentTeacher" type="Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--
複雜的屬性,需要單獨處理
對象:association
集合:collection
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
select *
from teacher
where id = #{id}
</select>
按照結果嵌套處理
<!--按照結果嵌套處理-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid, s.name sname, t.name tname
from student s,
teacher t
where s.tid = t.id;
</select>
<resultMap id="StudentTeacher2" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
接口
public interface StudentMapper {
// 查詢所有的學生資訊,以及對應的老師的資訊
public List<Student> getStudent();
public List<Student> getStudent2();
}
回顧 MySQL 多對一查詢方式:
- 子查詢 (對應按照查詢嵌套處理)
- 連表查詢 (對應按照結果嵌套處理)
11. 一對多處理
比如:一個老師擁有多個學生!
對于老師而言,就是一對多的關系。
測試環境搭建
同上
實體類
@Data
public class Teacher {
private int id;
private String name;
// 一個老師擁有多個學生
private List<Student> students;
}
@Data
public class Student {
private int id;
private String name;
private int tid;
}
按照查詢嵌套處理
<!--按查詢嵌套處理-->
<select id="getTeacher2" resultMap="TeacherStudent2">
select *
from mybatis.teacher
where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select *
from mybatis.student
where tid = #{tid}
</select>
按照結果嵌套處理
<!--按結果嵌套查詢-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname, t.name tname, t.id tid
from student s,
teacher t
where s.tid = t.id
and t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--複雜類型的屬性,需要單獨處理,對象:association 集合:collection
javaType="" 指定屬性的類型
集合中的泛型資訊,使用 ofType 擷取
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
小結
- 關聯 - association 【多對一】
- 集合 - collection 【一對多】
- javaType & ofType
- javaType 用來指定實體類中屬性的類型
- ofType 用來指定映射到 List 或者集合中的 pojo 類型,泛型中的限制類型
注意點:
- 保證 sql 的可讀性,盡量保證通俗易懂
- 注意一對多和多對一中的屬性名和字段的問題。
- 如果問題不好排查錯誤,可以使用日志,建議使用 Log4j
結果映射(resultMap)
- 用于在執行個體化類時,注入結果到構造方法中
constructor
- ID 參數;标記出作為 ID 的結果可以幫助提高整體性能
idArg
- 将被注入到構造方法的一個普通結果
arg
– 一個 ID 結果;标記出作為 ID 的結果可以幫助提高整體性能
id
– 注入到字段或 JavaBean 屬性的普通結果
result
– 一個複雜類型的關聯;許多結果将包裝成這種類型
association
- 嵌套結果映射 – 關聯可以是
元素,或是對其它結果映射的引用
resultMap
– 一個複雜類型的集合
collection
- 嵌套結果映射 – 集合可以是
元素,或是對其它結果映射的引用
resultMap
– 使用結果值來決定使用哪個
discriminator
resultMap
– 基于某些值的結果映射
case
- 嵌套結果映射 –
也是一個結果映射,是以具有相同的結構和元素;或者引用其它的結果映射
case
屬性 描述
id
目前命名空間中的一個唯一辨別,用于辨別一個結果映射。
type
類的完全限定名, 或者一個類型别名(關于内置的類型别名,可以參考上面的表格)。
autoMapping
如果設定這個屬性,MyBatis 将會為本結果映射開啟或者關閉自動映射。 這個屬性會覆寫全局的屬性 autoMappingBehavior。預設值:未設定(unset)。
面試高頻
- MySQL 引擎
- InnoDB 底層原理
- 索引
- 索引優化!
12. 動态 SQL
什麼是動态 SQL:動态 SQL 就是指根據不同的條件生成不同的 SQL 語句
利用動态 SQL 這一特性可以徹底擺脫這種痛苦。
使用動态 SQL 并非一件易事,但借助可用于任何 SQL 映射語句中的強大的動态 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。
如果你之前用過 JSTL 或任何基于類 XML 語言的文本處理器,你對動态 SQL 元素可能會感覺似曾相識。在 MyBatis 之前的版本中,需要花時間了解大量的元素。借助功能強大的基于 OGNL 的表達式,MyBatis 3 替換了之前的大部分元素,大大精簡了元素種類,現在要學習的元素種類比原來的一半還要少。
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
搭建環境
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '部落格id',
`title` VARCHAR(100) NOT NULL COMMENT '部落格标題',
`author` VARCHAR(30) NOT NULL COMMENT '部落格作者',
`create_time` DATETIME NOT NULL COMMENT '建立時間',
`views` INT(30) NOT NULL COMMENT '浏覽量'
)ENGINE=INNODB DEFAULT CHARSET=utf8
建立一個基礎工廠
- 導包
- 編寫配置檔案
- 編寫實體類
@Data public class Blog { private int id; private String title; private String author; private Date createTime; private int views; }
- 編寫實體類對于的 Mapper 接口和 Mapper.xml 檔案
if
<select id="queryBlogIf" parameterType="map" resultType="blog">
select *
from mybatis.blog
where 1 = 1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
choose (when, otherwise)
滿足一個選項後,後面的選項都不會進行了
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select *
from mybatis.blog
<where>
<choose>
<when test="title != null">
title= #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
trim (where, set)
<select id="queryBlogIf" parameterType="map" resultType="blog">
select *
from mybatis.blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
<update id="updateBlog" parameterType="map">
update mybatis.blog
<set>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id}
</update>
所謂的動态 SQL,本質還是 SQL 語句,隻是我們可以再 SQL 層面,去執行一些邏輯代碼。
SQL 片段
有的時候,我們可能會将一些功能的部分抽取出來,友善複用
- 使用 SQL 标簽抽取公共的部分
<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
- 在需要使用的時候,使用 include 标簽引用
<select id="queryBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <include refid="if-title-author"/> </where> </select>
注意事項:
- 最好基于單表來定義 SQL 片段。(如果SQL片段過于複雜,反而很難複用)
- 不要存在 where 标簽
Foreach
查詢 id 為 1 2 3 的記錄
抽象方法
// 查詢第 1~3 号id的部落格
List<Blog> queryBlogForeach(Map map);
配置檔案
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
測試
@Test
public void queryBlogForeach() {
SqlSession sqlSession = MyBatisUtils.getSqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap hashMap = new HashMap();
ArrayList<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
hashMap.put("ids", ids);
List<Blog> blogs = mapper.queryBlogForeach(hashMap);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
動态SQL就是在拼接SQL語句,我們隻要保證SQL的正确性,按照SQL的格式,去排列組合即可
建議:
- 先在 MySQL 中寫出完整的 SQL,再對應地去修改為我們地動态 SQL 實作通用即可。
13. 緩存
13.1 簡介
查詢 : 連接配接資料庫,耗資源
一次查詢的結果,給他暫存再一個可以直接取到的地方 --> 記憶體 : 緩存
當再次查詢時,直接走緩存,就不用走資料庫了
- 什麼是緩存[ Cache ]?
- 存在記憶體的臨時資料
- 将使用者經常查詢的資料放在緩存(記憶體)中,使用者去查詢資料就不用從磁盤上(關系型資料庫資料檔案)查詢,從緩存中查詢,進而提高查詢效率,解決了高并發系統的性能問題。
- 為什麼使用緩存?
- 減少和資料庫的互動次數,減少系統開銷,提高系統效率。
- 什麼樣的資料能使用緩存?
- 經常查詢并且不經常改變的資料。
13.2 MyBatis 緩存
- MyBatis 包含一個非常強大的查詢緩存特性,它可以非常友善地定制和配置緩存,緩存可以極大的提升查詢效率。
- MyBatis 系統中預設定義了兩級緩存:一級緩存和二級緩存
- 預設i情況下,隻有一級緩存開啟。(SqlSession 級别的緩存,也稱為本地緩存)
- 二級緩存需要手動開啟和配置,他是基于 namespace 級别的緩存。
- 為了提高可擴充性,MyBatis 定義了緩存接口 Cache。我們可以通過實作 Cache 接口來自定義二級緩存。
13.3 一級緩存
- 一級緩存也叫本地緩存:
- 與資料庫同一次會話期間查到的資料會放到本地緩存中。
- 以後如果需要擷取相同的資料,直接從緩存中拿,沒必要再去查詢資料庫
測試步驟:
- 開啟日志
- 測試一個 Session 中查詢兩次相同記錄
@Test public void test() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); System.out.println(user); User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user == user2); sqlSession.close(); }
- 檢視日志輸出
... PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Opening JDBC Connection Created connection 322836221. ==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, origami, 123456 <== Total: 1 User(id=1, name=origami, pwd=123456) User(id=1, name=origami, pwd=123456) true Closing JDBC Connection [com.mysql.jdbc.J[email protected]] Returned connection 322836221 to pool.
緩存失效的情況
- 查詢不同的東西
- 增删改操作,可能會改變原來的資料,是以必定重新整理緩存!
==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, origami, 123456 <== Total: 1 ==> Preparing: update mybatis.user set name = ?, pwd=? where id = ?; ==> Parameters: 123(String), 23214(String), 2(Integer) <== Updates: 1 ==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, origami, 123456 <== Total: 1 User(id=1, name=origami, pwd=123456)
- 查詢不同的 Mapper.xml
- 手動清理緩存
@Test public void test() { SqlSession sqlSession = MyBatisUtils.getSqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.queryUserById(1); // 清理緩存 sqlSession.clearCache(); User user2 = mapper.queryUserById(1); System.out.println(user2); System.out.println(user == user2); sqlSession.close(); }
==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, origami, 123456 <== Total: 1 ==> Preparing: select * from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, name, pwd <== Row: 1, origami, 123456 <== Total: 1
小結:一級緩存是預設開啟的,隻在一個 SqlSession 中有效,也就是拿到連接配接到關閉連接配接這個區間段。
一級緩存就是一個 Map
13.4 二級緩存
- 二級緩存也叫全局緩存,一級緩存作用域太低了,是以誕生了二級緩存
- 基于 namespace 級别的緩存,一個名稱空間,對應一個二級緩存
- 工作機制
- 一個會話查詢一條語句,整個資料就會放在目前會話的一級緩存中
- 如果目前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的資料被儲存到二級緩存中
- 新的會話查詢資訊,就可以從二級緩存中擷取内容
- 不同的 mapper 查出的資料會放在自己對于的緩存 (map) 中
步驟:
- 開啟全局緩存
設定名 描述 有效值 預設值 cacheEnabled 全局性地開啟或關閉所有映射器配置檔案中已配置的任何緩存。 true | false true <!--開啟全局緩存--> <setting name="cacheEnabled" value="true"/>
- 在要使用二級緩存的 Mapper 中開啟
也可也自定義一些參數<!--在目前 Mapper.xml 中使用二級緩存--> <cache/>
<!--在目前 Mapper.xml 中使用二級緩存--> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
- 測試
- 問題:我們需要将實體類序列化,否則就會報錯
Caused by: java.io.NotSerializableException: com.tt.pojo.User
public class User implements Serializable { ... }
- 問題:我們需要将實體類序列化,否則就會報錯
小結:
- 隻要開啟了二級緩存,在同一個 Mapper 下就有效。
- 所有的資料都會先放在一級緩存中。
- 隻有當會話送出,或者關閉的時候,才會送出到二級緩存中!
13.5 緩存原理
[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-m0bDpSB9-1626154721540)(pic/mybatis-緩存原理.png)]
13.6 自定義緩存-ehcache
Ehcache是一種廣泛使用的開源Java分布式緩存。主要面向通用緩存
要在程式中使用 ehcache,先要導包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.1</version>
</dependency>
在 mapper 中指定我們的 ehcache
ehcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--
diskStore:為緩存路徑,ehcache分為記憶體和磁盤兩級,此屬性定義磁盤的緩存位置。參數解釋如下:
user.home – 使用者主目錄
user.dir – 使用者目前工作目錄
java.io.tmpdir – 預設臨時檔案路徑
-->
<diskStore path="java.io.tmpdir/Tmp_EhCache"/>
<!--
defaultCache:預設緩存政策,當ehcache找不到定義的緩存時,則使用這個緩存政策。隻能定義一個。
-->
<!--
name:緩存名稱。
maxElementsInMemory:緩存最大數目
maxElementsOnDisk:硬碟最大緩存個數。
eternal:對象是否永久有效,一但設定了,timeout将不起作用。
overflowToDisk:是否儲存到磁盤,當系統當機時
timeToIdleSeconds:設定對象在失效前的允許閑置時間(機關:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,預設值是0,也就是可閑置時間無窮大。
timeToLiveSeconds:設定對象在失效前允許存活時間(機關:秒)。最大時間介于建立時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,預設是0.,也就是對象存活時間無窮大。
diskPersistent:是否緩存虛拟機重新開機期資料 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個參數設定DiskStore(磁盤緩存)的緩存區大小。預設是30MB。每個Cache都應該有自己的一個緩沖區。
diskExpiryThreadIntervalSeconds:磁盤失效線程運作時間間隔,預設是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache将會根據指定的政策去清理記憶體。預設政策是LRU(最近最少使用)。你可以設定為FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:記憶體數量最大時是否清除。
memoryStoreEvictionPolicy:可選政策有:LRU(最近最少使用,預設政策)、FIFO(先進先出)、LFU(最少通路次數)。
FIFO,first in first out,這個是大家最熟的,先進先出。
LFU, Less Frequently Used,就是上面例子中使用的政策,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的将會被清出緩存。
LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離目前時間最遠的元素将被清出緩存。
-->
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>