1. 程式人生 > >MyBatis框架的學習(二)——MyBatis架構與入門

MyBatis框架的學習(二)——MyBatis架構與入門

MyBatis框架的架構

MyBatis框架的架構如下圖:
這裡寫圖片描述
下面作簡要概述:

  1. SqlMapConfig.xml,此檔案作為mybatis的全域性配置檔案,配置了mybatis的執行環境等資訊。mapper.xml檔案即sql對映檔案,檔案中配置了操作資料庫的sql語句,此檔案需要在SqlMapConfig.xml中載入。
  2. 通過mybatis環境等配置資訊構造SqlSessionFactory(即會話工廠)。
  3. 由會話工廠建立sqlSession即會話,操作資料庫需要通過sqlSession進行。
  4. mybatis底層自定義了Executor執行器介面操作資料庫,Executor介面有兩個實現,一個是基本執行器、一個是快取執行器。
  5. MappedStatement也是mybatis一個底層封裝物件,它包裝了mybatis配置資訊及sql對映資訊等。mapper.xml檔案中一個sql對應一個MappedStatement物件,sql的id即是MappedStatement的id
  6. MappedStatement對sql執行輸入引數進行定義,包括HashMap、基本型別、pojo,Executor通過MappedStatement在執行sql前將輸入的java物件對映至sql中,輸入引數對映就是JDBC程式設計中對preparedStatement設定引數。
  7. MappedStatement對sql執行輸出結果進行定義,包括HashMap、基本型別、pojo,Executor通過MappedStatement在執行sql後將輸出結果對映至java物件中,輸出結果對映過程相當於JDBC程式設計中對結果的解析處理過程。

MyBatis入門

mybatis下載

mybaits的程式碼由github.com管理,地址:https://github.com/mybatis/mybatis-3/releases。大家可從該地址下載mybatis最新框架。
本人下載的是mybatis-3.2.7,其目錄結構為:
這裡寫圖片描述

開發需求

現在我們明確需求,即實現以下功能:

  1. 根據使用者id查詢一個使用者資訊
  2. 根據使用者名稱稱模糊查詢使用者資訊列表
  3. 新增使用者
  4. 更新使用者
  5. 刪除使用者

MyBatis入門程式配置

【第一步】,使用Eclipse建立一個普通的Java工程,例如mybatis-day01。
【第二步】,加入Jar包。工程所需加入的Jar包有mybatis核心包、依賴包和mysql資料驅動包。如下:
這裡寫圖片描述


【第三步】,在classpath下建立日誌記錄檔案——log4j.properties。我們可在mybatis-day01工程下新建一個config原始碼包,並在該原始碼包下建立一個日誌記錄檔案——log4j.properties
這裡寫圖片描述
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

mybatis預設使用log4j作為輸出日誌資訊。
【第四步】,在classpath下建立SqlMapConfig.xml檔案。我們同樣可在config原始碼包下建立該檔案
這裡寫圖片描述
其內容如下:

<?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" />
            <!-- 資料庫連線池 -->
            <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="yezi" />
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlMapConfig.xml是mybatis的核心配置檔案,以上檔案的配置內容為資料來源、事務管理。
注意:等後面mybatis和Spring兩個框架整合之後,environments的配置將被廢除。
【第五步】,建立一個po類——User.java。我們可在src目錄下新建一個名為cn.itheima.mybatis.po的包,並在該包下建立一個po類——User.java
這裡寫圖片描述
po類作為mybatis進行sql對映使用,po類通常與資料庫表對應,User.java檔案的內容如下:

public class User {
    private int id;
    private String username;// 使用者姓名
    private String sex;// 性別
    private Date birthday;// 生日
    private String address;// 地址
    public int getId() {
        return id;
    }
    public void setId(int 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 + "]";
    }
}

【第六步】,在classpath下的sqlmap目錄下建立sql對映檔案user.xml
這裡寫圖片描述
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">
<mapper namespace="test">

</mapper>

namespace:即名稱空間,其用於隔離sql語句(即不同sql對映檔案中的兩個相同id的sql語句如何來區分),這是當前的作用,後面會講另一層非常重要的作用。
【第七步】,載入對映檔案。mybatis框架需要載入對映檔案,將user.xml新增在SqlMapConfig.xml中,如下:
這裡寫圖片描述
故須在SqlMapConfig.xml配置檔案中新增如下配置資訊:

<mappers>
    <!-- resource是基於classpath來查詢的 -->
    <mapper resource="sqlmap/user.xml"/>
</mappers>

MyBatis入門程式測試——根據id查詢使用者資訊

在user.xml對映檔案中新增如下配置:

<!-- 根據id獲取使用者資訊 -->
<select id="getUserById" parameterType="int" resultType="cn.itheima.mybatis.po.User">
    select * from user where id=#{id};
</select>
  • parameterType:查詢引數的資料型別,即定義輸入到sql中的對映型別。
  • resultType:查詢結果的資料型別,如果是pojo則應該給出全路徑。
  • #{id}表示使用PreparedStatement設定佔位符號並將輸入變數id傳到sql中。說白點,#{}作用就是佔位符,相當於JDBC中的?

接著在src目錄下新建一個名為cn.itheima.mybatis.first的包,並在該包下建立一個MybatisTest單元測試類,緊接著在該類中編寫如下一個但單元測試方法:

public class MybatisTest {

    @Test
    public void getUserById() throws IOException {
        // 第一步,建立SqlSessionFactoryBuilder物件
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 第二步,載入配置檔案
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 第三步,建立SqlSessionFactory物件
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        // 第四步,建立SqlSession物件
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 第五步,使用SqlSession物件執行查詢,得到User物件
        // 第一個引數:執行查詢的StatementId
        User user = sqlSession.selectOne("getUserById", 10);
        // 第六步,列印結果
        System.out.println(user);
        // 第七步,釋放資源,每一個sqlSession就是一個連線
        sqlSession.close();
    }

}

以上完成的需求就是根據id查詢使用者資訊。一般來講工廠物件一般在實際開發是單例的,並不需要頻繁地建立,故getUserById()方法可優化為:

public class MybatisTest {

    private SqlSessionFactory sqlSessionFactory = null; // 工廠物件一般在我們的系統中是單例的

    @Before
    public void init() throws IOException {
        // 第一步,建立SqlSessionFactoryBuilder物件
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 第二步,載入配置檔案
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 第三步,建立SqlSessionFactory物件
        sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void getUserById() throws IOException {
        // 第四步,建立SqlSession物件
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 第五步,使用SqlSession物件執行查詢,得到User物件
        // 第一個引數:執行查詢的StatementId
        User user = sqlSession.selectOne("getUserById", 10);
        // 第六步,列印結果
        System.out.println(user);
        // 第七步,釋放資源,每一個sqlSession就是一個連線
        sqlSession.close();
    }
}

根據使用者名稱稱模糊查詢使用者資訊列表

現在我就來完成第二個需求——根據使用者名稱稱模糊查詢使用者資訊列表。
一開始我在user.xml對映檔案中新增如下配置:

<select id="getUserByName" parameterType="string" resultType="cn.itheima.mybatis.po.User">
    SELECT * FROM `user` WHERE username LIKE #{username}
</select>

如果查詢結果返回的是List集合,那麼resultType只需要設定為List集合中的一個元素的資料型別即可。
接著在MybatisTest單元測試類編寫如下單元測試方法:

@Test
public void getUserByName() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 執行查詢
    List<User> list = sqlSession.selectList("getUserByName", "%張%");
    for (User user : list) {
        System.out.println(user);
    }
    // 釋放資源
    sqlSession.close();
}

執行以上方法,Eclipse控制檯列印如下:
這裡寫圖片描述
實際開發中建議使用#{}佔位符這種方式,因為這樣可以防止SQL注入。除了以上這種方式外,在此我還介紹第二種方式,即在user.xml對映檔案中新增如下配置:

<select id="getUserByName" parameterType="string" resultType="cn.itheima.mybatis.po.User">
    SELECT * FROM `user` WHERE username LIKE '%${value}%'
</select>

${value}表示使用引數將${value}替換,做字串的拼接,${}為字串拼接指令。注意:如果是取簡單資料型別的引數,括號中的值必須為value。
如此一來,MybatisTest單元測試類中的getUserByName()方法應修改為:

@Test
public void getUserByName() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 執行查詢
    List<User> list = sqlSession.selectList("getUserByName", "張");
    for (User user : list) {
        System.out.println(user);
    }
    // 釋放資源
    sqlSession.close();
}

執行以上方法,Eclipse控制檯列印如下:
這裡寫圖片描述
很明顯這種方式在實際開發中是不建議使用的,因為無法防止SQL注入。

總結

#{}和${}

#{}:表示一個佔位符號,可以很好地去避免sql注入。其原理是將佔位符位置的整個引數和sql語句兩部分提交給資料庫,資料庫去執行sql語句,去表中匹配所有的記錄是否和整個引數是否一致。
#{}要獲取輸入引數的值:

  • 如果輸入引數是簡單資料型別,則#{}中可以寫value或其它名稱。
  • 如果輸入引數是pojo物件型別,則#{}可通過OGNL方式去獲取,表示式就是屬性.屬性.屬性....方式。

${}表示一個sql拼接符號,其原理是在向資料庫發出sql之前去拼接好sql再提交給資料庫執行。
${}要獲取輸入引數的值:

  • 如果輸入引數是簡單資料型別,則${}中只能寫value。
  • 如果輸入引數是pojo物件型別,則${}可通過OGNL方式去獲取,表示式就是屬性.屬性.屬性....方式。

一般情況下建議使用#{},特殊情況下必須要用${},比如:

  1. 動態拼接sql中動態組成排序欄位,要通過${}將排序欄位傳入sql中。
  2. 動態拼接sql中動態組成表名,要通過${}將表名傳入sql中。

parameterType和resultType

parameterType:指定輸入引數型別,mybatis通過ognl從輸入物件中獲取引數值拼接在sql中。
resultType:指定輸出結果型別,mybatis將sql查詢結果的一行記錄資料對映為resultType指定型別的物件。

selectOne()和selectList()方法

selectOne查詢一條記錄,如果使用selectOne查詢多條記錄則丟擲異常:
這裡寫圖片描述
selectList可以查詢一條或多條記錄。

新增使用者

現在我就來完成第三個需求——新增使用者。首先在user.xml對映檔案中新增如下配置:

<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
    INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>

如果輸入引數是pojo,那麼#{}中的名稱就是pojo類中的屬性(用到了物件圖導航的思想),而不能隨便寫了。
然後試著在MybatisTest單元測試類編寫如下單元測試方法:

@Test
public void addUser() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 建立User物件
    User user = new User();
    user.setUsername("小喬");
    user.setBirthday(new Date());
    user.setSex("2");
    user.setAddress("上海");
    // 插入使用者
    sqlSession.insert("insertUser", user);
    // 釋放資源
    sqlSession.close();
}

執行以上方法,發現Eclipse控制檯中列印:
這裡寫圖片描述
雖然發出了sql語句,但是事務並沒將其提交,而是回滾了。故User物件是無法插入到資料庫user表中的。所以addUser()單元測試方法應修改為:

@Test
public void addUser() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 建立User物件
    User user = new User();
    user.setUsername("小喬");
    user.setBirthday(new Date());
    user.setSex("2");
    user.setAddress("上海");
    // 插入使用者
    sqlSession.insert("insertUser", user);
    // 提交事務
    sqlSession.commit();
    // 釋放資源
    sqlSession.close();
}

執行以上方法,Eclipse控制檯會列印:
這裡寫圖片描述
事務已提交,可發現數據庫user表中插入一條記錄。

MySQL自增主鍵返回

現在有這樣一個需求:想要得到MySQL資料庫給我們生成的主鍵id,即獲取主鍵。那如何實現這個需求呢?這裡要用到MySQL資料庫中的一個函式:

  • LAST_INSERT_ID():返回auto_increment自增列新記錄id值。該函式是在當前事務下取到你最後生成的id值,而我們應知道查詢操作是沒有開啟事務的,增刪改操作是需要開啟事務的。

如此一來,需要將user.xml對映檔案中的如下配置:

<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
    INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>

修改為(即新增selectKey實現將主鍵返回):

<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
    <selectKey keyProperty="id" resultType="int" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO `user` (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>
  • keyProperty:返回的主鍵儲存在pojo中的哪個屬性(即其對應pojo的主鍵屬性)。獲取主鍵,實際上是將主鍵取出來之後封裝到了pojo的主鍵屬性當中。
  • resultType:返回的主鍵是什麼型別(即其對應pojo的主鍵的資料型別)。
  • order:selectKey的執行順序,是相對於insert語句來說的,由於mysql的自增原理,執行完insert語句之後才將主鍵生成,所以這裡selectKey的執行順序為AFTER。

最後將addUser()單元測試方法應修改為:

@Test
public void addUser() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 建立User物件
    User user = new User();
    user.setUsername("大喬");
    user.setBirthday(new Date());
    user.setSex("2");
    user.setAddress("上海");
    // 插入使用者
    sqlSession.insert("insertUser", user);
    System.out.println(user.getId());
    // 提交事務
    sqlSession.commit();
    // 釋放資源
    sqlSession.close();
}

執行以上方法,Eclipse控制檯會列印:
這裡寫圖片描述

MySql使用uuid實現主鍵

使用uuid實現主鍵,需要增加通過select uuid()語句得到uuid值作為主鍵。所以需要將user.xml對映檔案中id為insertUser的Statement改置為:

<insert id="insertUser" parameterType="cn.itheima.mybatis.po.User">
<selectKey resultType="java.lang.String" order="BEFORE" 
keyProperty="id">
    select uuid()
</selectKey>
insert into user(id,username,birthday,sex,address) 
    values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>

因為是使用uuid做主鍵,所以應該先生成主鍵然後再插入資料,此時order屬性的值應是BEFORE。

刪除使用者

現在我就來完成第四個需求——刪除使用者。首先在user.xml對映檔案中新增如下配置:

<!-- 刪除使用者 -->
<delete id="deleteUser" parameterType="int">
    DELETE FROM `user` WHERE id=#{id1}
</delete>

然後在MybatisTest單元測試類編寫如下單元測試方法:

@Test
public void deleteUser() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 刪除使用者
    sqlSession.delete("deleteUser", 29);
    // 提交事務
    sqlSession.commit();
    // 釋放資源
    sqlSession.close();
}

更新使用者

現在我就來完成第五個需求——修改使用者資訊。首先在user.xml對映檔案中新增如下配置:

<!-- 修改使用者資訊 -->
<update id="updateUser" parameterType="cn.itheima.mybatis.po.User">
    UPDATE `user` set username=#{username} WHERE id=#{id}
</update>

然後在MybatisTest單元測試類編寫如下單元測試方法:

@Test
public void updateUser() {
    // 建立一個SqlSession物件
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 建立一個User物件
    User user = new User();
    user.setUsername("張角");
    user.setId(10);
    // 更新使用者
    sqlSession.update("updateUser", user);
    // 提交事務
    sqlSession.commit();
    sqlSession.close();
}

MyBatis解決了JDBC程式設計的問題

還記得MyBatis框架的學習(一)——MyBatis介紹一文中使用JDBC程式設計所帶來的問題嗎?現在使用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需要具有很強的經驗和能力才行。
總之,按照使用者的需求在有限的資源環境下只要能做出維護性、擴充套件性良好的軟體架構都是好架構,所以框架只有適合才是最好。

我個人總結的MyBatis與Hibernate這兩個框架的不同之處:

  • MyBatis學習成本低,入門門檻低。MyBatis需要程式設計師自己寫sql,對sql修改和優化就比較靈活。MyBatis是不完全的ORM框架,MyBatis需要程式設計師編寫sql,但是MyBatis也存在對映(輸入對映、輸出對映)適用場景:需求變化較快的專案開發,比如網際網路專案、電商。
  • Hibernate學習成本高,入門門檻高,Hibernate是ORM框架,不需要程式設計師編寫sql,自動根據物件對映生成sql。適用場景:需求固定的中小型專案,如OA系統、ERP系統。

企業在技術選型上應考慮各個技術框架的特點去進行選型,企業要根據人力、物力等資源去衡量,以節省成本利潤最大化為目標進行選型。
至此,我們就算入門MyBatis了。讀者如需檢視原始碼,可參考!