1. 程式人生 > >MyBatis 學習 (一) 入門

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 statement物件

  • 設定sql語句

  • 設定sql語句中的引數(使用preparedStatement)

  • 通過statement執行sql並獲取結果

  • 對sql執行結果進行解析處理

  • 釋放資源(resultSet、preparedstatement、connection)

jdbc問題總結如下:

  • 資料庫連線建立、釋放頻繁造成系統資源浪費,從而影響系統性能。如果使用資料庫連線池可解決此問題。

  • Sql語句在程式碼中硬編碼,造成程式碼不易維護,實際應用中sql變化的可能較大,sql變動需要改變java程式碼。

  • 使用preparedStatement向佔有位符號傳引數存在硬編碼,因為sql語句的where條件不一定,可能多也可能少,修改sql還要修改程式碼,系統不易維護。

  • 對結果集解析存在硬編碼(查詢列名),sql變化導致解析程式碼變化,系統不易維護,如果能將資料庫記錄封裝成pojo/bean物件解析比較方便。

MyBatis架構
這裡寫圖片描述

  • mybatis配置
    SqlMapConfig.xml,此檔案作為mybatis的全域性配置檔案,配置了mybatis的執行環境等資訊。
    mapper.xml檔案即sql對映檔案,檔案中配置了操作資料庫的sql語句。此檔案需要在SqlMapConfig.xml中載入。
    通過mybatis環境等配置資訊構造SqlSessionFactory即會話工廠

由會話工廠建立sqlSession即會話,操作資料庫需要通過sqlSession進行。

mybatis底層自定義了Executor執行器介面操作資料庫,Executor介面有兩個實現,一個是基本執行器、一個是快取執行器。

Mapped Statement也是mybatis一個底層封裝物件,它包裝了mybatis配置資訊及sql對映資訊等。mapper.xml檔案中一個sql對應一個MappedStatement物件,sql的id即是Mapped statement的id。

MappedStatement對sql執行輸入引數進行定義,包括HashMap、基本型別、pojo,Executor通過Mapped Statement在執行sql前將輸入的java物件對映至sql中,輸入引數對映就是jdbc程式設計中對preparedStatement設定引數。

Mappedstatement對sql執行輸出結果進行定義,包括HashMap、基本型別、pojo,Executor通過Mapped Statement在執行sql後將輸出結果對映至java物件中,輸出結果對映過程相當於jdbc程式設計中對結果的解析處理過程。

入門案例(增刪改查)

根據id查詢使用者

表結構如下
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=27 DEFAULT CHARSET=utf8;

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>
   <!--
      environments 環境配置(資料庫連線資訊)
   和spring整合後 environments配置將廢除 -->
   <environments default="development">
      <environment id="development">
         <!-- 使用jdbc事務管理 -->
         <transactionManager type="JDBC" />
         <!-- 資料庫連線池 -->
         <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver" />
            <property name="url"
               value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
            <property name="username" value="root" />
            <property name="password" value="root" />
         </dataSource>
      </environment>
   </environments>

   <!-- 告訴總配置檔案  表配置檔案在哪兒 -->
   <mappers>
      <mapper resource="sqlmap/User.xml"/>
   </mappers>

</configuration>

bean

public class User implements Serializable {
    /**
     *
     */
    private static final long serialVersionUID = 1L;
    private Integer id;
    private String username;// 使用者姓名
    private String sex;// 性別
    private Date birthday;// 生日
    private String address;// 地址


    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", sex=" + sex
                + ", birthday=" + birthday + ", address=" + address + "]";
    }

}

user.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,還有一個很重要的作用,後面會講 -->
<!-- 寫SQL語句 -->
<mapper namespace="test">
   <!-- 通過id查詢一個使用者
      id:用於識別方法呼叫時,呼叫哪個sql語句
      parameterType:引數型別
      resultType:返回結果型別 (包名+類名) 使用此屬性要注意bean中屬性與資料庫中的要一致!
      #{} :佔位符,相當於jdbc的 ?
   -->
   <select id="findUserById" parameterType="Integer" resultType="cn.itcast.bean.User">
      select * from user where id = #{id}

      <!-- #{} 
parameterType是值型別裡面隨便添點東西,不寫會報錯 -->
   </select>

</mapper>

測試

package cn.itcast.MyBatis_day02;

import java.io.IOException;
import java.io.InputStream;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import cn.itcast.bean.User;
import junit.framework.TestCase;

public class AppTest  extends TestCase
{
   @Test
   public void testName () throws IOException
   {
      //載入核心配置檔案
      String resource = "SqlMapConfig.xml" ;
      InputStream in = Resources.getResourceAsStream(resource) ;

      //建立SqlSessionFactory
      SqlSessionFactory ssf = new SqlSessionFactoryBuilder ().build(in) ;

      //建立SqlSession
      SqlSession sqlSession = ssf.openSession() ;

      //執行sql語句
      User user = sqlSession.selectOne("test.findUserById" , 1) ;

      System.out.println(user);

   }
}

根據使用者名稱模糊查詢

   <!--
      #{} 佔位符 這種寫法可以防止sql注入 (推薦第三種)
      ${} 字串拼接 使用${} 裡面只能填value
    -->
   <select id="findUserByName" parameterType="String" resultType="cn.itcast.bean.User">
      <!-- select * from user where username like #{name}  -->
      <!-- select * from user where username like '%${value}%'  -->
       select * from user where username like "%"#{name}"%"
   </select>
   //根據使用者名稱查詢使用者資訊
   @Test
   public void testName () throws Exception

   {
      String resource = "SqlMapConfig.xml" ;
      InputStream in = Resources.getResourceAsStream(resource) ;

      SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in) ;

      SqlSession ss =  ssf.openSession() ;

//    User user =  ss.selectOne("test.findUserByName", "%三豐%");
      User user =  ss.selectOne("test.findUserByName", "三豐");

      System.out.println(user);
   }

#{}和${}

#{}表示一個佔位符號,通過#{}可以實現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查詢多條記錄則丟擲異常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
   at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查詢一條或多條記錄。

**插入一條新資料**
-------

//新增
@Test
public void testInsert () throws Exception

{
String resource = “SqlMapConfig.xml” ;
InputStream in = Resources.getResourceAsStream(resource) ;

  SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in) ;

  SqlSession ss =  ssf.openSession() ;

  User user = new User () ;
  user.setUsername("哈哈");
  user.setBirthday( new Date ());
  user.setSex("男");
  user.setAddress("d");

  int i = ss.insert("test.insertUser", user) ;

  //提交事務
  ss.commit();

}

<!-- #{username} 此時#{} 作用像是取值,從user物件取出username屬性的值  -->
   <insert id="insertUser" parameterType="cn.itcast.bean.User">
      insert into User ( username , birthday , sex , address )values ( #{username} , #{birthday} , #{sex} , #{address} )
   </insert>

在插入新資料後,返回新資料的主鍵id
------------------

//新增
@Test
public void testInsert () throws Exception

{
String resource = “SqlMapConfig.xml” ;
InputStream in = Resources.getResourceAsStream(resource) ;

  SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in) ;

  SqlSession ss =  ssf.openSession() ;

  User user = new User () ;
  user.setUsername("哈哈");
  user.setBirthday( new Date ());
  user.setSex("男");
  user.setAddress("d");

  int i = ss.insert("test.insertUser", user) ;

  //提交事務
  ss.commit();

  System.out.println(user.getId());

}

  <!-- #{username} 此時#{} 作用像是取值,從user物件取出username屬性的值  -->
   <insert id="insertUser" parameterType="cn.itcast.bean.User">
      <!-- 獲取剛剛插入新資料的主鍵id (這是一條SQL語句) -->
      <!-- selectKey 標籤實現主鍵返回 -->
      <!-- keyColumn:主鍵對應的表中的哪一列 -->
      <!-- keyProperty:主鍵對應的pojo中的哪一個屬性 -->
      <!-- order:設定在執行insert語句前執行查詢id的sql,還是在執行insert語句之後執行查詢id的sql -->
      <!-- resultType:設定返回的id的型別 -->
      <selectKey keyColumn="id" keyProperty="id" order="AFTER" resultType="Integer">
         select LAST_INSERT_ID()
      </selectKey>

      insert into User ( username , birthday , sex , address )values ( #{username} , #{birthday} , #{sex} , #{address} )
   </insert>

使用uuid實現主鍵
----------

思路:在insert之前執行
select uuid()得到uuid值 返回值填充到bean的id中,然後#{}取出即可
- <insert id="insert" parameterType="com.mawulou.model.Functions" >
--     <selectKey keyProperty="pkGlobalId" resultType="String" order="BEFORE">
-         select  uuid()
-     </selectKey>
--     insert into tbl_function (id, name, parent_id,
-       sort, url)
-     values (#{id}, #{name}, #{parentId},
-       #{sort}, #{url})
-   </insert>



## 更新資料 ##

<update id="updateUser" parameterType="cn.itcast.bean.User">
      update user set username = #{username} where id = #{id}
   </update>

//更新
      @Test
      public void testUpdate () throws Exception

      {
         String resource = "SqlMapConfig.xml" ;
         InputStream in = Resources.getResourceAsStream(resource) ;

         SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in) ;

         SqlSession ss =  ssf.openSession() ;

         User user = new User () ;
         user.setUsername("呵呵");
         user.setId(27);
         ss.update("updateUser", user) ;

         //提交事務
         ss.commit();


         ss.close();
      }

刪除資料
<delete id="deleteUser" parameterType="Integer">
      delete from user where id = #{id}
   </delete>


//刪除
            @Test
            public void testDelete () throws Exception

            {
               String resource = "SqlMapConfig.xml" ;
               InputStream in = Resources.getResourceAsStream(resource) ;

               SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in) ;

               SqlSession ss =  ssf.openSession() ;


               ss.delete("deleteUser", 29) ;

               //提交事務
               ss.commit();


               ss.close();
            }
    1. Mybatis解決jdbc程式設計的問題

.
資料庫連線建立、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用資料庫連線池可解決此問題。
解決:在SqlMapConfig.xml中配置資料連線池,使用連線池管理資料庫連結。

Sql語句寫在程式碼中造成程式碼不易維護,實際應用sql變化的可能較大,sql變動需要改變java程式碼。
解決:將Sql語句配置在XXXXmapper.xml檔案中與java程式碼分離。

向sql語句傳引數麻煩,因為sql語句的where條件不一定,可能多也可能少,佔位符需要和引數一一對應。
解決:Mybatis自動將java物件對映至sql語句,通過statement中的parameterType定義輸入引數的型別。

對結果集解析麻煩,sql變化導致解析程式碼變化,且解析前需要遍歷,如果能將資料庫記錄封裝成pojo物件解析比較方便。
解決:Mybatis自動將sql執行結果對映至java物件,通過statement中的resultType定義輸出結果的型別。

    1. 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需要具有很強的經驗和能力才行。
總之,按照使用者的需求在有限的資源環境下只要能做出維護性、擴充套件性良好的軟體架構都是好架構,所以框架只有適合才是最好。
“`