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="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="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="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="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解析時,每個
