目錄
- 自定義持久層框架
- jdbc 代碼基礎回顧
- 解決傳統jdbc存在的問題
- 自定義持久層框架設計思路
- 使用端 --> 專案
- 自定義持久層框架本身
- mybatis 簡介
- sqlMapConfig.xml 節點說明
- 傳統dao層開發方式
- dao層開發方式
- mybatis 外部properties 檔案
- mybatis aliasType
- mapper.xml
- 動態sql
- mybatis復雜映射開發
- 一對一查詢
- 一對多查詢
- 多對多查詢
- mybatis 注解開發
- 注解一對多
- 注解多對多
- mybatis 快取
- 一級快取
- 一級快取分析
- 二級快取
- redis-cache 分析
- mybatis的插件
- 以上組件的創建原理
- 自定義mybatis 插件
- mybatis的第三方插件
- mybatis架構原理
- mybatis傳統方式原始碼分析
- Mapper代理方式
- 設計模式
- mybatis 執行流程圖解
- mybatis延遲加載
- mybatis 動態sql相關類
- 常用的資料庫連接池
- mybatis 查詢資料量大的時候會造成oom 如何處理
- jdbc 代碼基礎回顧
2020年8月25日20:55:29
自定義持久層框架
- 持久層框架 是對jdbc 的封裝, 并解決了jdbc存在的問題
jdbc 代碼基礎回顧
try{
Class.formName("com.mysql.jdbc.Driver");
connection = .....
}cache(Exception e){
}
- 傳統jdbc 存在 資料庫配置資訊存在硬編碼問題 當更換資料庫后需要再次對資料庫驅動資訊進行更改
- 頻繁創建釋放資料庫連接
- sql陳述句 設定引數 獲取結果集等引數均存在硬編碼問題
- 手動封裝結果集 較為繁瑣
解決傳統jdbc存在的問題
- 硬編碼 --> 組態檔
- 硬編碼 --> 連接池
- 手動封裝結果集 --> 反射 內省
自定義持久層框架設計思路
使用端 --> 專案
- 引入自定義持久層jar
- 提供資料庫配置資訊及sql配置資訊
- sql配置資訊
- sql陳述句
- 引數型別
- 回傳值型別
- sql配置資訊
- 使用組態檔提供外上述兩種配置資訊
- sqlMapConfig.xml 存放資料庫的配置資訊,
存入mapper.xml全路徑 - mapper.xml 存放sql配置資訊
自定義持久層框架本身
-
創建為專案工程,本質是對jdbc代碼進行封裝
-->底層為傳統的jdbc- 資料庫配置資訊
- sql 配置資訊 占位符 引數 sql陳述句等
- 根據呼叫處傳遞的配置資訊進行決議
- 加載組態檔
-->以位元組輸入流的形式加載-->存盤在記憶體中 - 創建Resource類,getResourceAsStream(String path)
- 創建2個javaBean(容器物件),存放的是對組態檔決議出的內容
- Configuration:核心配置類: 存放sqlMapperConfig.xml決議的內容
- MappedStrement:映射配置類:存放mapper.xml決議的內容
- 決議組態檔:Dom4j
- 創建類:SqlSessionFactoryBuilder,存在方法:build(InputStream in)
- build方法實作邏輯:
- 使用dom4j決議組態檔,將決議結果封裝到容器物件內
- 創建sqlSessionFactory物件,生產sqlSession(會話物件),避免重復創建連接
-->工廠模式 降低耦合
- 創建sqlSessionFactory介面及實作類DefaultSqlSessionFactory
- openSession方法 : 創建 sqlSession物件
- 創建SqlSession 介面及實作類DefaultSqlSession 定義對資料庫的CRUD方法
- selectList()
- selectOne()
- update()
- delete()
- 創建Excutor介面及實作類 SimpleExcutor 實作類
- query(Configuration conf,MappedStatement mapped,Object ...params):執行JDBC代碼
- 加載組態檔
graph TB b("工程專案") subgraph 工程專案 d("mapper.xml") --"mapper.xml全路徑"--> c("sqlMapConfig.xml") b --"sql執行陳述句"--> d b --"資料庫配置"--> c end b --"參考"--> a subgraph 自定義持久層 tip("本質是對jdbc代碼的封裝") a("自定義持久層") tip --> a a --"1. 創建"--> a1("Resources類") a1 --> a1a("InputStream()") a1 --"加載組態檔為位元組輸入流"--> a1b("getResourceAsStream(String path)") a --"2. 創建"--> a2("容器javaBean") subgraph 容器 a2 --"創建核心配置類"--> a2a("Configuration") a2a --"存放"--> a21a("sqlMapConfig.xml 決議的內容") a2 --"創建核心配置類"--> a2b("MappedStatement") a2b --"映射配置類"--> a2b1("mapper.xml 決議的內容") end a --"3. 創建"--> a3("SqlSessionFactory") a3 --"實作類"--> a3a("DefaultSqlSessionFactory") a3a --"方法"--> a3a1("openSession()") a3a1 --"生產"--> a3a1a("Sqlsession") a --"4. 決議組態檔dom4j"--> a4("SqlSessionFactoryBuilder") a4 --"方法"--> a41("build(inputStream in)") a41 --"使用dom4j決議組態檔" --> a4a1a("決議結果") a4a1a --"封裝到容器物件"--> a2 a41 --"創建"--> a41b("SqlSessionFactory物件") a41b --"工廠模式生產"--> a41b1("sqlSession會話物件") a --"5. 創建"--> a5("SqlSession介面") a5 --"實作類"--> a5a("DefaultSession") a5a --"串列查詢"--> a5a1("selectList()") a5a --"查詢單個"--> a5a2("selectONe()") a5a --"修改"--> a5a3("update()") a5a --"洗掉"--> a5a4("delete()") a --"6. 創建"--> a6("Excutor介面") a6 --"實作類"--> a6a("SimpleExcutor") a6a --"成員方法"--> a6a1("query(Configuration conf,MappedStatement st,Object ...params);") a6a1 --"執行jdbc代碼" -->a6a1a("sql陳述句") end
mybatis 簡介
-
持久層框架
-
基于
orm:Object Relation Mapping物體類和資料庫表建立映射關聯關系 -
半自動 可以對sql進行優化
hibernate -> 全自動 -
輕量級:啟動的程序中需要的資源比較少
-
底層: 對jdbc執行代碼 進行封裝 規避硬編碼,頻繁開閉資料源
sqlMapConfig.xml 節點說明
- environments 資料庫環境的配置,支持多環境配置
- environments.default
-->指定mybatis 使用的默認環境名稱 - environment.id
-->當前環境的名稱 - environment - transcationManager.type="JDBC" 指定事務管理型別為jdbc
- environment - dataSource.type="POOLED" 指定資料源型別為連接池
- environment - dataSource
- property.driver 資料庫驅動
- property.url 資料庫地址 注意連接編碼格式
- property.user 資料庫連接用戶名
- property.password 資料庫連接用密碼
- mapper 配置的四種方式:
mapper.resource相對于類路徑的參考mapper.url完全限定資源定位符mapper.class介面對應的全路徑package.name批量加載 保證映射檔案與介面同包同名
傳統dao層開發方式
dao層開發方式
mybatis 外部properties 檔案
- 設定mybatis xml組態檔的約束頭
- 在resources下創建外部組態檔 格式:
物件.屬性=值 - 加載外部組態檔
- 在 configutation 后第一個節點之前 引入外部組態檔
<properties resource='路徑'/>
- 在 configutation 后第一個節點之前 引入外部組態檔
mybatis aliasType
- 在mybatis 組態檔中的typeAliases 節點中設定
<typeAliases> <typeAlias type="pojo類路徑的全限定名" alais="別名"></typeAlias> </typeAliases> // 對于基礎資料型別 mybatis 已定義了別名 string ==> String long ==> Long int ==> Integer double ==> Double boolean ==> Boolean - 當存在多個pojo時不適用以上方法,采用package模式[批量起別名]
- package模式
不要單獨指定alias屬性 默認為類本身的類名,且不區分大小寫
<typeAliases> <package name="pojo類所在包的全限定名"></typeAlias> // 不要單獨指定alias屬性 默認為類本身的類名,且不區分大小寫 </typeAliases> - package模式
mapper.xml
動態sql
if標簽 判斷入參是否符合條件, 不符合不拼接where標簽 自動拼接where 同時去掉where后的第一個and 關鍵字foreach標簽collectionarray 注意便攜式不要使用#{}open回圈前追加close回圈結束后追加item單次回圈內的資料物件 生成的變數separator前后回圈資料之間的分隔符
sql標簽 抽取sql片段- id sql陳述句的標識
- 內容為具體的sql陳述句 可用于封裝 分頁 和 查詢某張表時的前面部分
mybatis復雜映射開發
sqlMapConfig.xml 內引入 mapper.xml
<package name="與介面同包同名的包名"><package>
一對一查詢
resultMap手動配置物體屬性與表欄位的映射關系 可用于mybatis 的resultMMap- id
- type 按照封裝物件的全路徑
<resultMap id="orderMap" type="com.lagou.pojo.Order"> <result property="id" column="id"></result> <result property="orderTime" column="orderTime"></result> <result property="total" column="total"></result> <association property="user" javaType="com.lagou.pojo.User"> <result property="id" column="uid"></result> <result property="username" column="username"></result> </association> </resultMap> <!--resultMap:手動來配置物體屬性與表欄位的映射關系--> <select id="findOrderAndUser" resultMap="orderMap"> select * from orders o,user u where o.uid = u.id </select>
一對多查詢
<resultMap id="userMap" type="com.lagou.pojo.User">
<result property="id" column="uid"></result>
<result property="username" column="username"></result>
<collection property="orderList" ofType="com.lagou.pojo.Order">
<result property="id" column="id"></result>
<result property="orderTime" column="orderTime"></result>
<result property="total" column="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select * from user u left join orders o on u.id = o.uid
</select>
多對多查詢
- 查詢用戶的同時查詢出用戶的角色
<resultMap id="userRoleMap" type="com.lagou.pojo.User">
<result property="id" column="userid"></result>
<result property="username" column="username"></result>
<collection property="roleList" ofType="com.lagou.pojo.Role">
<result property="id" column="roleid"></result>
<result property="roleName" column="roleName"></result>
<result property="roleDesc" column="roleDesc"></result>
</collection>
</resultMap>
<select id="findAllUserAndRole" resultMap="userRoleMap">
select * from user u left join sys_user_role ur on u.id = ur.userid
left join sys_role r on r.id = ur.roleid
</select>
mybatis 注解開發
使用注解開發 無需撰寫任何組態檔
- @Insert 增
- @Update 刪
- @Delete 改
- @Select 查
- @Result 實作對結果集的封裝 替代result標簽節點
- @Results 替代resultMap 標簽節點
- @One 替代單個pojo物件 替代 association 標簽節點
- @Many 如果屬性為集合時 則采用 此注解
注解一對多
@Results({
@Result(property = "id",column = "id"),
@Result(property = "orderTime",column = "orderTime"),
@Result(property = "total",column = "total"),
@Result(property = "user",column = "uid",javaType = User.class,
one=@One(select = "com.lagou.mapper.IUserMapper.findUserById"))
})
@Select("select * from orders")
public List<Order> findOrderAndUser();
注解多對多
# IUserMapper
//查詢所有用戶、同時查詢每個用戶關聯的角色資訊
@Select("select * from user")
@Results({
@Result(property = "id",column = "id"),
@Result(property = "username",column = "username"),
@Result(property = "roleList",column = "id",javaType = List.class,
many = @Many(select = "com.lagou.mapper.IRoleMapper.findRoleByUid"))
})
public List<User> findAllUserAndRole();
# IRoleMapper
@Select("select * from sys_role r,sys_user_role ur where r.id = ur.roleid and ur.userid = #{uid}")
public List<Role> findRoleByUid(Integer uid);
mybatis 快取
一級快取
使用同一個sqlSession 物件時,資料會被快取

- 第一次發起查詢用戶id為1的用戶資訊,先去找快取中是否有id為1的用戶資訊,如果沒有,從
資料庫查詢用戶資訊,得到用戶資訊,將用戶資訊存盤到一級快取中, - 如果中間sqlSession去執行commit操作(執行插入、更新、洗掉),則會清空SqlSession中的
一級快取,這樣做的目的為了讓快取中存盤的是最新的資訊,避免臟讀, - 第二次發起查詢用戶id為1的用戶資訊,先去找快取中是否有id為1的用戶資訊,快取中有,直
接從快取中獲取用戶資訊
一級快取分析

- 一級快取到底是什么
底層是一個hashMap 參照上述流程圖 - 一級快取什么時候被創建
快取key創建類: Excustor.class.createCacheKey() BaseExcutor.createCacheKey() //創建一級快取的key CacheKey.update() - updateList.add(configuration.getEnviroment().getId()); - 一級快取的作業流程是怎樣的
- 一級快取的清空
sqlSession.close()或者執行帶有事務的操作 - 在分布式環境下也會存在
臟讀問題解決:禁用一級快取或調整快取為statement 級別
二級快取
- 原理 類似 一級快取
- 操作的sqlSession 執行了事務操作后 會清空二級快取
- 一級快取 默認開啟
- 二級快取需要手動配置開啟
1. xml開發形式 <setting> <setting name="cacheEnabled" value="https://www.cnblogs.com/Yount/p/true" /> </setting> 2. 注解形式 在dao介面上添加注解 @CacheNamespace - 二級快取 快取的不是物件 而是物件中的資料
- pojo類需要實作 Serializable 介面
- 二級快取的存盤機制是多樣的,可能存盤在硬碟上 或者記憶體中
- 當執行帶有事務的操作后,二級快取會被清空
- 當基于xml配置時還可以配置
- useCache 屬性 false 禁用二級快取 每次查詢都發送sql去資料庫查詢 默認為true
注解形式:@select 注解之前添加 @Options(cache="false")- flushCache 屬性: 每次增刪改操作后 清空快取 默認true
二級快取整合redis

PerpetualCache是mybatis默認實作快取功能的類- 二級快取的底層資料結構 還是 HashMap
org.apache.ibatis.cache.impl.PerpetualCache --> private Map<Object, Object> cache = new HashMap() - 指定mybatis二級快取的實作類
在dao介面上添加注解 @CacheNamespace(implementation=PerpetualCache.class) PerpetualCache.class 更換為自定義快取實作類 如 : @CacheNamespace(implementation = RedisCache.class) - 二級快取 存在的問題
- 單服務器下 --> 沒問題
- 分布式 --> 無法實作分布式快取
- 分布式快取技術
- redis 官方提供有mybatis-redis實作類
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> 配置組態檔 : redis.properties 配置以下屬性: host=localhost port=6379 connectionTimeout=5000 password=123456 database=0- memcached
- ehcache
- 二級快取存在的臟讀問題分析

redis-cache 分析
- redis-cache 是如何存取值的
1. 必須實作mybatis 的cache 介面 2. 使用 jedis.hget 進行取值 - redis-cache 使用的是哪種redis 資料結構
redis 的 hash 資料型別
mybatis的插件
- 一種形式的拓展點
- 增加靈活性
- 根據實際需求 自行拓展
- mybatis 的四大組件 及允許攔截的方法
- Executor 執行器
- update
- query
- commit
- rollback 等
- StatementHandler sql語法構建器
- prepare
- parameterize
- batch
- update
- query 等
- ParameterHandler 引數處理器
- getParameterObject
- setParameterObject
- ResultSetHandler 結果集處理器
- handleResultSets
- handleOutputParameters
- Executor 執行器
- 插件
攔截器底層是對以上四個組件的攔截,采用動態代理實作
以上組件的創建原理
- 每個創建出來的物件不是直接回傳的,而是
interceptorChain.pluginAll(parameterHandler) - 獲取到所有的Interceptor(攔截器)(插件需要實作的介面);呼叫
interceptor.plugin(target);回傳target包裝后的物件 - 插件機制,我們可以使用插件為目標物件創建一個代理物件;AOP(面向切面)我們的插件可以為四大物件創建出代理物件,代理物件就可以攔截到四大物件的每一個執行;
- 由于使用了jdk動態代理,那么就回去執行 invoke 方法,可以在攔截器 的前置和后置處理器內做相應的處理操作
@Intercepta({
@signature(
type=Executor.class,
method="query",
args=(MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class}
)
})
public class ExamplePlugin implements Interceptor{
//省略邏輯
}
自定義mybatis 插件
- 插件 --> 攔截器
org.apache.ibatis.plugin.Interceptor 類
# 攔截方法:只要被攔截的目標物件的目標方法被執行時,每次都會執行intercept方法
Interceptor.intercept
# 主要為了把當前的攔截器生成代理存到攔截器鏈中
Interceptor.plugin
# 獲取組態檔的引數
Interceptor.setProperties
- 定義
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.sql.Connection;
import java.util.Properties;
# 可以多個Signature
@Intercepts({
@Signature(type= StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}),
@Signature(type= Executor.class,method = "prepare",args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {
/*
攔截方法:只要被攔截的目標物件的目標方法被執行時,每次都會執行intercept方法
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("對方法:" + invocation.getMethod().getName() + " 進行了增強...." );
return invocation.proceed(); //原方法執行
}
/*
主要為了把當前的攔截器生成代理存到攔截器鏈中
*/
@Override
public Object plugin(Object target) { Object wrap = Plugin.wrap(target, this); return wrap; }
/*
獲取組態檔的引數
*/
@Override
public void setProperties(Properties properties) { System.out.println("獲取到的組態檔的引數是:"+properties); }
}
- 配置
<plugins>
<plugin interceptor="com.lagou.plugin.MyPlugin">
<property name="name" value="https://www.cnblogs.com/Yount/p/tom"/> <!-- 設定引數 -->
</plugin>
</plugins>
- 使用
1. MyPlugin.plugin --> MyPlugin.intercept --> MyPlugin.setProperties
mybatis的第三方插件
- PageHelper
- 匯入maven 依賴
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency>- 在mybatis組態檔中配置PageHelper
在 plugins 節點內 添加 plugin 并設定屬性 `property` 方言 `dialect` 值設 `mysql` <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="https://www.cnblogs.com/Yount/p/mysql"/> </plugin> - 通用 mapper
- 匯入依賴
<dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.1.2</version> </dependency>- mybatis 的組態檔內配置 通用mapper 的plugin
<plugin interceptor="tk.mybatis.mapper.mapperhelper.MapperInterceptor"> <!--指定當前通用mapper介面使用的是哪一個--> <property name="mappers" value="https://www.cnblogs.com/Yount/p/tk.mybatis.mapper.common.Mapper"/> </plugin>- 物體類設定關聯
@Table(name = "user") public class User implements Serializable { @Id //對應的是注解id @GeneratedValue(strategy = GenerationType.IDENTITY) // @GeneratedValue strategy 設定主鍵的生成策略 // GenerationType.IDENTITY 底層必須支持自增長 // GenerationType.SEQUENCY 底層不支持自增長 // GenerationType.TABLE 從表中取值 生成主鍵 // GenerationType.AUTO 自動選擇合適的生成主鍵 private Integer id; @Column(name="123") 當與表的欄位不同時,采用@Column 保持同步 private String username; // Get Set 略 }- 創建dao介面 繼承
tk.mybatis.mapper.common.Mapper傳入泛型
egg: public interface UserMapper extends Mapper<User> {}- 使用
- 傳統呼叫
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = new User(); user.setId(1); User user1 = mapper.selectOne(user); System.out.println(user1);Example方式呼叫
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); // example 方式呼叫 Example example = new Example(User.class); // 查詢User表中的全部記錄 example.createCriteria().andEqualTo("id",1); // 執行查詢,獲得結果 List<User> users = mapper.selectByExample(example); for (User user2 : users) { System.out.println(user2); }
隨堂測驗01 自定義持久層框架
-
自定義持久層框架IPersistence是如何解決JDBC存在的問題:() [多選題]
- [x] A. 用組態檔解決了硬編碼問題
- [x] B.使用了C3P0連接池解決了頻繁創建釋放資料庫連接問題
- [x] C.在simpleExecute中使用到了反射進行了引數的設定
- [x] D.在simpleExecute中使用了內省進行了回傳結果集的封裝
-
在進行自定義持久層框架IPersistence優化時,主要為了解決那些問題:() [多選題]
- [x] A.應用在Dao層,整個操作的模板代碼重復
- [x] B.呼叫sqlSession方法時、引數statementId硬編碼
- [ ] C.無法保證statementId的唯一性
- [ ] D.引數存在硬編碼
-
下列關于Configuration及MappedStatement配置類,說法正確的是:() [多選題]
- [x] A.使用dom4j對sqlMapConfig.xml決議時,會將決議出來的內容以不同形式封裝到Configuration物件中
- [ ] B.使用dom4j對mapper.xml決議時,每個
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/195919.html
標籤:Java
