MyBatis學習筆記(一)

MyBatis概述

MyBatis本是Apache的一個開源項目iBatis, 2010年這個項目由apache software foundation遷移到了google code,並且改名爲MyBatis。2013年11月遷移到Github。

MyBatis 是一個優秀的持久層框架,它對jdbc的操作數據庫的過程進行封裝,使開發者只需要關注 SQL 本身,而不需要花費精力去處理例如註冊驅動、 創建connection、創建 statement、手動設置參數、結果集檢索等 jdbc繁雜的過程代碼。

Mybatis 通過 xml 或註解的方式將要執行的各種 statement(statement、 preparedStatemnt、CallableStatement)配置起來,並通過 java 對象和 statement 中的 sql 進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。

使用JDBC存在的問題

JDBC的編程步驟

  1. 加載數據庫驅動
  2. 創建獲取數據庫連接
  3. 創建Statement對象
  4. 執行SQL語句並返回ResultSet結果集
  5. 處理結果集
  6. 釋放資源

存在的問題

  1. 數據庫連接創建、釋放頻繁造成系統資源浪費,從而影響系統性能。如果使用數據庫連接池可解決此問題。
  2. SQL語句在代碼中硬編碼,造成代碼不易維護,實際應用中SQL變化的可能較大,SQL語句變動需要改變 java代碼。
  3. 使用preparedStatement向佔位符傳參數存在硬編碼,因爲sql語句的where條件不一定,可能多也可能少,修改sql還要修改代碼,系統不易維護。
  4. 對結果集解析存在硬編碼(查詢列名),sql變化導致解析代碼變化,系統不易維護,如果能將數據庫記錄封裝成pojo對象解析比較方便。

MyBatis架構

MyBatis架構圖

圖1 MyBatis架構圖

架構圖解釋

MyBatis配置文件

  • SqlMapConfig.xml

    MyBatis的全局配置文件,配置了MyBatis的運行環境等信息

  • SqlSessionFactory

    會話工廠,用於創建SqlSession,通過MyBatis環境等配置信息構造SqlSessionFactory

  • SqlSession

    會話,操作數據庫主要通過SqlSession進行

  • Executor

    MyBatis底層自定義了Executor執行器接口操作數據庫,Executor接口有兩個實現,一個是基本執行器、一個是緩存執行器

  • MappedStatement

    Mapped Statement也是mybatis一個底層封裝對象,它包裝了mybatis配置信息及sql映射信息等。mapper.xml文件中一個sql對應一個Mapped Statement對象,sql的id即是Mapped statement的id。

    • 輸入映射

      Mapped Statement對sql執行輸入參數進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql前將輸入的java對象映射至sql中,輸入參數映射就是jdbc編程中對preparedStatement設置參數

    • 輸出映射

      Mapped Statement對sql執行輸出結果進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql後將輸出結果映射至java對象中,輸出結果映射過程相當於jdbc編程中對結果的解析處理過程

Mybatis入門程序

MyBatis下載

MyBatis下載地址

點擊上述鏈接進入頁面後,再點擊下圖中的位置即可下載;下載完畢後壓縮包中有MyBatis的依賴包(在lib目錄下)。

圖2

環境配置

  1. 導入MyBatis依賴包以及數據庫連接驅動

  2. 編寫SqlMapConfig.xml配置文件,配置文件的編寫如下:

    <?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>
    <!--  與spring整合以後,environments配置將被廢除  -->
    <environments default="development">
       <environment id="development">
           <!--事務管理,使用jdbc-->
           <transactionManager type="jdbc"></transactionManager>
           <!--數據庫連接池-->
           <dataSource type="POOLED">
               <!--配置數據庫連接信息-->
               <property name="driver" value="com.mysql.jdbc.Driver"></property>
               <property name="url" value="jdbc:mysql://localhost:3306/mybatistest?characterEncoding=UTF-8"></property>
               <property name="username" value="數據庫用戶名"></property>
               <property name="password" value="數據庫密碼"></property>
           </dataSource>
       </environment>
    </environments>
    </configuration>
    
  3. 編寫日誌輸出文件log4j.properties,日誌輸出文件的編寫如下:

    # Global logging configuration
    log4j.rootLogger=DEBUG, stdout
    # Console output...
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
    
  4. 在數據庫中創建表,我創建的是user表:

    CREATE TABLE `user` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(32) NOT NULL COMMENT '用戶名稱',
    `birthday` date DEFAULT NULL COMMENT '生日',
    `sex` char(1) DEFAULT NULL COMMENT '性別',
    `address` varchar(256) DEFAULT NULL COMMENT '地址',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;
    
  5. 編寫數據庫中表的對應的pojo(Plain Ordinary Java Object,其實就是Java Bean)

  6. 編寫UserMapper.xml,寫法如下:

    <?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:命名空間,用於隔離SQL
    ->
    <mapper namespace="com.xurenyi.pojo.User">
    <!--
       id:sql的id
       parameterType:輸入參數的類型
       resultType:輸出結果的類型,應該填寫對應POJO的全路徑
       #{v}:佔位符,相當於jdbc中的?
    -->
    <select id="findUserById" parameterType="Integer"   resultType="com.xurenyi.pojo.User">
       select * from user where id=#{v}
    </select>
    </mapper>
    
  7. 在SqlMapperConfig.xml中配置UserMapper文件,寫法如下:

    <!--配置UserMapper.xml文件的位置-->
    <mappers>
       <mapper resource="config/UserMapper.xml"></mapper>
    </mappers>
    

案例

根據id查詢單個用戶

  1. 在UserMapper.xml中編寫SQL語句:
    <!--
    mapper:用於編寫SQL語句,mapper元素中可以寫多個SQL語句
    namespace:命名空間,用於隔離SQL
    -->
    <mapper namespace="com.xurenyi.pojo.User">
    <!--
       id:SQL語句的id
       parameterType:輸入參數的類型
       resultType:輸出結果的類型,應該填寫對應POJO的全路徑
       '#{v}':佔位符,相當於jdbc中的?
    -->
    <!--
       根據id查詢單個用戶
    -->
    <select id="findUserById" parameterType="Integer" resultType="com.xurenyi.pojo.User">
       select * from user where id=${value}
    </select>
    </mapper>
    
  2. 編寫測試方法:
    //根據id查找單個用戶
    @Test
    public void findUserById() throws IOException {
       //加載SqlMapConfig.xml配置文件,把配置文件的路徑作爲參數傳入
       InputStream in =  Resources.getResourceAsStream("config/SqlMapConfig.xml");
       //創建SqlSessionFactoryBuilder
       SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
       //獲取SqlSessionFactory(根據配置文件創建SqlSessionFactory)
       SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in);
       //獲取SqlSession
       SqlSession sqlSession = sqlSessionFactory.openSession();
       //執行select操作,傳入參數是SQL語句的id、SQL語句中的查找參數
       User user = sqlSession.selectOne("findUserById", 1);
       System.out.println(user);
    }
    

注意: 由於下面的例子中測試代碼的前面部分與根據id查詢單個用戶中的測試代碼是一樣的,所以在下述的例子中,就只寫SQL語句(寫在<mapper></mapper>元素中)以及通過sqlSession進行操作數據庫的語句(例如根據id查詢單個用戶測試代碼中的User user = sqlSession.selectOne("findUserById", 1);這句),省略獲取SqlSession對象的代碼。

根據用戶名模糊查詢用戶

  • 方式一
    • SQL語句
      <!--模糊查詢-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like #{v}
      </select>
      
    • 測試代碼
      /*
      獲取SqlSession對象的代碼,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "%張%");
      
  • 方式二
    • SQL語句
      <!--模糊查詢-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like '%${value}%'
      </select>
      
    • 測試代碼
      /*
      獲取SqlSession對象的代碼,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "張");
      
  • 方式三
    • SQL語句
      <!--模糊查詢-->
      <select id="findUserListByLike"
             parameterType="String"
             resultType="com.xurenyi.pojo.User">
         select * from user where username like "%"#{v}"%"
      </select>
      
    • 測試代碼
      /*
      獲取SqlSession對象的代碼,略
      */
      List<User> userList = sqlSession.selectList("findUserListByLike", "張");
      

添加用戶

  • SQL語句
    <!--添加用戶
    參數類型是User對象,所以在SQL語句中取參數值的時候,需要把User實體類裏面的屬性名寫進去,而不能在'#{}'裏面隨便寫幾個字母
    -->
    <insert id="addUser" parameterType="com.xurenyi.pojo.User">
       <!--取參數的時候需要把User裏面的屬性寫進'#{}',而不能隨便寫-->
       insert into user values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>
    
  • 測試代碼
    /*
    獲取SqlSession對象的代碼,略
    */
    User user = new User();
    user.setId(null);
    user.setUsername("用戶名");
    user.setBirthday(new Date());
    user.setSex("男");
    user.setAddress("用戶地址");
    // 需要"改變"數據庫中數據的操作(例如:插入、刪除、修改數據)都有一個int類型的返回值,該返回值表示對數據庫中記錄影響的行數
    int addUser = sqlSession.insert("addUser", user);
    sqlSession.commit();   //需要自己提交事務(在增刪改查這四個操作中,除了查詢操作,其餘三個操作都需要提交事務)
    

添加用戶返回ID

  • 需求:在使用MyBatis插入一個用戶數據以後,獲取該用戶的id。
  • select LAST_INSERT_ID();
    • 這是MySQL中提供的查詢最新添加數據的id,但是執行該條語句時,需要先執行一條插入語句才能返回id,否則就返回0。
  • SQL語句
    <insert id="addUser" parameterType="com.xurenyi.pojo.User">
       <!--
           標籤實現主鍵返回
           keyColumn:主鍵對應數據庫表中的哪一列
           keyProperty:主鍵對應pojo中的哪一個屬性
           order:設置在執行insert語句之前執行查詢id的操作,還是在執行insert語句之後執行查詢id的操作
       -->
       
       <selectKey keyColumn="id" keyProperty="id" resultType="Integer" order="AFTER">
           select LAST_INSERT_ID()
       </selectKey>
       
       <!--取參數的時候需要把User裏面的屬性寫進#{},而不能隨便寫-->
       insert into user values(#{id},#{username},#{birthday},#{sex},#{address})
    </insert>
    
  • 測試代碼
    /*
    獲取SqlSession對象的代碼,略
    */
    User user = new User();
    user.setId(null);
    user.setUsername("用戶名");
    user.setBirthday(new Date());
    user.setSex("男");
    user.setAddress("用戶地址");
    // 需要"改變"數據庫中數據的操作(例如:插入、刪除、修改數據)都有一個int類型的返回值,該返回值表示對數據庫中記錄影響的行數
    int addUser = sqlSession.insert("addUser", user);
    sqlSession.commit(); //需要自己提交事務(在增刪改查這四個操作中,除了查詢操作,其餘三個操作都需要提交事務)
    System.out.println(user.getId()); //在提交事務之後獲取id並打印
    

根據id修改用戶數據

  • SQL語句
    <!--根據id修改用戶數據-->
    <update id="updateUserById" parameterType="com.xurenyi.pojo.User">
       update user set address=#{address},birthday=#{birthday}  where id=#{id}
    </update>
    
  • 測試代碼
    /*
    獲取SqlSession對象的代碼,略
    */
    User user = new User();
    user.setId(28);
    user.setAddress("修改後的地址");
    user.setUsername("修改後的姓名");
    user.setSex("女");
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date = simpleDateFormat.parse("1994-01-01 17:06:00");
    user.setBirthday(date);
    sqlSession.update("updateUserById", user);
    //提交事務
    sqlSession.commit();
    //關閉sqlSession
    sqlSession.close();
    

根據id刪除用戶

  • SQL語句
    <!--根據id刪除用戶數據-->
    <delete id="deleteUserById" parameterType="Integer">
       delete from user where id=#{id}
    </delete>
    
  • 測試代碼
    /*
    獲取SqlSession對象的代碼,略
    */
    int count=sqlSession.delete("deleteUserById",28);
    //提交事務
    sqlSession.commit();
    System.out.println(count);
    //關閉sqlSession
    sqlSession.close();
    

小結

  • #{}和${}的區別
    • #{}
      #{}表示一個佔位符號,通過#{}可以實現preparedStatement向佔位符中設置值,自動進行java類型和jdbc類型轉換。#{}可以有效防止sql注入。 #{}可以接收簡單類型值或pojo屬性值。 如果parameterType傳輸單個簡單類型值, #{}括號中可以是 value 或其它名稱。
    • $ {}
      $ {}表示拼接 sql 串,通過$ {}可以將parameterType傳入的內容拼接在sql中且不進行 jdbc類型轉換,$ {}可以接收簡單類型值或 pojo 屬性值,如果 parameterType 傳輸單個簡單類型值,${}括號中只能是 value。
  • parameterType和resultType
    • parameterType
      指定輸入參數類型,mybatis通過ognl從輸入對象中獲取參數值拼接在 sql 中。
    • resultType
      指定輸出結果類型,mybatis 將 sql 查詢結果的一行記錄數據映射爲resultType 指定類型的對象。如果有多條數據,則分別進行映射,並把對象放到容器 List 中。
  • selectOne和selectList
    • selectOne
      selectOne用於查詢一條記錄,如果使用selectOne查詢多條記錄會報錯。
    • selectList
      selectList用於查詢一條或多條記錄。
  • MyBatis解決JDBC編程的問題
    • 問題1

      • 描述:數據庫連接創建、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用數據庫連接池可解決此問題
      • 解決:在 SqlMapConfig.xml 中配置數據連接池,使用連接池管理數據庫鏈接
    • 問題2

      • 描述:Sql 語句寫在代碼中造成代碼不易維護,實際應用 sql 變化的可能較大,sql 變動需要改變 java代碼
      • 解決:將 Sql 語句配置在 XXXXmapper.xml 文件中與 java 代碼分離
    • 問題3

      • 描述:向sql語句傳參數麻煩,因爲sql語句的where條件不一定,可能多也可能少,佔位符需要和參數一一對應
      • 解決:Mybatis 自動將 java 對象映射至 sql 語句,通過 statement 中的 parameterType 定義輸入參數的類型
    • 問題4

      • 描述:對結果集解析麻煩,sql變化導致解析代碼變化,且解析前需要遍歷,如果 能將數據庫記錄封裝成pojo對象解析比較方便
      • 解決:Mybatis自動將sql執行結果映射至java對象,通過statement中的 resultType定義輸出結果的類型
  • MyBatis和Hibernate的異同
    • Mybatis和hibernate不同,它不完全是一個ORM框架,因爲MyBatis需要程序員自己編寫Sql語句。mybatis可以通過XML或註解方式靈活配置要運行的sql語句,並將java對象和sql語句映射生成最終執行的sql,最後將sql執行的結果再映射生成java對象。
    • Mybatis學習門檻低,簡單易學,程序員直接編寫原生態sql,可嚴格控制sql執行性能,靈活度高,非常適合對關係數據模型要求不高的軟件開發,例如:互聯網軟件、企業運營類軟件等,因爲這類軟件需求變化頻繁,一旦需求變化要求成果輸出迅速。但是靈活的前提是 mybatis 無法做到數據庫無關性,如果需要實現支持多種數據庫的軟件則需要自定義多套 sql 映射文件,工作量大。
    • Hibernate 對象/關係映射能力強,數據庫無關性好,對於關係模型要求高的軟件(例如需求固定的定製化軟件)如果用hibernate開發可以節省很多代碼,提高效率。但是Hibernate的學習門檻高,要精通門檻更高,而且怎麼設計O/R映射,在性能和對象模型之間如何權衡,以及怎樣用好Hibernate需要具有很強的經驗和能力才行。
    • 總之,按照用戶的需求在有限的資源環境下只要能做出維護性、擴展性良好的軟件架構都是好架構,所以框架只有適合纔是最好。

SqlMapConfig.xml配置文件詳解

SqlMapConfig.xml中配置的內容和順序

圖3 SqlMapConfig.xml中配置的內容和順序
這裏選講幾個元素。

  • properties(屬性)

    • properties是用來加載外部配置文件的,其寫法如下:

      <!--使用resource加載外部配置文件-->
      <properties resource="外部配置文件的路徑">
         <!--如果外部配置文件中有該屬性,那麼內部定義的屬性被外部屬性覆蓋-->
         <property name="" value=""/>
      </properties>
      
    • MyBatis加載屬性的順序

      先讀取properties元素體內自定義的屬性,再讀取properties中resource指定路徑的外部配置文件中的屬性,如果在外部配置文件中有同名屬性,則後者會覆蓋前者。

  • typeAliases(類型別名)

    有時候寫實體類的全路徑比較麻煩,所以可以取一個別名,這樣可以在Mapper.xml中直接寫別名調用。

    • 定義單個別名

      定義單個別名的配置寫法如下:

      <typeAliases>
         <!--定義單個別名
             type:需要被取別名的對象全路徑
             alias:對應的別名
         -->
         <typeAlias type="com.xurenyi.pojo.User" alias="user"></typeAlias>
      </typeAliases>
      
    • 批量定義別名

      批量定義別名的配置寫法如下:

      <typeAliases>
        <!--批量定義別名
            會掃描整個包下的類,別名是類名(大小寫不敏感)
        -->
        <package name="com.xurenyi.pojo"></package>
      
    ```
  • mappers(映射器)

    • 用法1

      <mapper resource="mapper.xml配置文件的全路徑"></mapper>

    • 用法2
      <mapper class="對應的mapper類的路徑"></mapper>

      此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。

    • 用法3
      <package name="包路徑"></mapper>

      此種方法要求mapper接口名稱和mapper映射文件名稱相同,且放在同一個目錄中。
      實際項目開發中,會使用這種方法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章