mybatis 框架學習筆記


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語句
      • 參數類型
      • 返回值類型
  • 使用配置文件提供外上述兩種配置信息
  • 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='路徑'/>
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>
    
mapper.xml
動態sql
  • if 標簽 判斷入參是否符合條件, 不符合不拼接
  • where 標簽 自動拼接where 同時去掉where后的第一個and 關鍵字
  • foreach 標簽
    • collection array 注意便攜式不要使用#{}
    • 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 對象時,數據會被緩存
mybatis 一級緩存過程圖解

  1. 第一次發起查詢用戶id為1的用戶信息,先去找緩存中是否有id為1的用戶信息,如果沒有,從
    數據庫查詢用戶信息。得到用戶信息,將用戶信息存儲到一級緩存中。
  2. 如果中間sqlSession去執行commit操作(執行插入、更新、刪除),則會清空SqlSession中的
    一級緩存,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀。
  3. 第二次發起查詢用戶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

mybatis二級緩存結構示意圖

  • 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
  • 二級緩存存在的臟讀問題分析
    二級緩存namespace級別產生臟讀問題分析
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
  • 插件攔截器底層是對以上四個組件的攔截,采用動態代理實現
以上組件的創建原理
  1. 每個創建出來的對象不是直接返回的,而是interceptorChain.pluginAll(parameterHandler)
  2. 獲取到所有的Interceptor(攔截器)(插件需要實現的接口);調用interceptor.plugin(target);返回target包裝后的對象
  3. 插件機制,我們可以使用插件為目標對象創建一個代理對象;AOP(面向切面)我們的插件可以為四大對象創建出代理對象,代理對象就可以攔截到四大對象的每一個執行;
  4. 由於使用了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 自定義持久層框架

  1. 自定義持久層框架IPersistence是如何解決JDBC存在的問題:() [多選題]

    • [x] A. 用配置文件解決了硬編碼問題
    • [x] B.使用了C3P0連接池解決了頻繁創建釋放數據庫連接問題
    • [x] C.在simpleExecute中使用到了反射進行了參數的設置
    • [x] D.在simpleExecute中使用了內省進行了返回結果集的封裝
  2. 在進行自定義持久層框架IPersistence優化時,主要為了解決那些問題:() [多選題]

    • [x] A.應用在Dao層,整個操作的模板代碼重復
    • [x] B.調用sqlSession方法時、參數statementId硬編碼
    • [ ] C.無法保證statementId的唯一性
    • [ ] D.參數存在硬編碼
  3. 下列關於Configuration及MappedStatement配置類,說法正確的是:() [多選題]

    • [x] A.使用dom4j對sqlMapConfig.xml解析時,會將解析出來的內容以不同形式封裝到Configuration對象中
    • [ ] B.使用dom4j對mapper.xml解析時,每個


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM