1. 程式人生 > >MyBatis框架的學習(三)——Dao層開發方法

MyBatis框架的學習(三)——Dao層開發方法

使用MyBatis開發Dao層,通常有兩個方法,即原始Dao開發方法和Mapper介面開發方法。本文案例程式碼的編寫是建立在前文MyBatis框架的學習(二)——MyBatis架構與入門案例基礎之上的!

需求

明確開發需求,在實際開發中,我們總歸是要開發Dao層的,所以在本文中我使用MyBatis這個框架技術開發Dao層來將以下功能一一實現:

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

MyBatis常用API的使用範圍

在講解使用MyBatis這個框架技術開發Dao層之前,首先來說一下MyBatis常用的API。

SqlSessionFactoryBuilder的使用範圍

SqlSessionFactoryBuilder用於建立SqlSessionFacoty,SqlSessionFacoty一旦建立完成就不需要SqlSessionFactoryBuilder了,因為SqlSession是通過SqlSessionFactory生產,所以可以將SqlSessionFactoryBuilder當成一個工具類使用,最佳使用範圍是方法範圍(即方法體內區域性變數)。

SqlSessionFactory的使用範圍

SqlSessionFactory是一個介面,介面中定義了openSession的不同過載方法,SqlSessionFactory的最佳使用範圍是整個應用執行期間,一旦建立後可以重複使用,通常以單例模式管理SqlSessionFactory。

SqlSession的使用範圍

SqlSession中封裝了對資料庫的操作,如查詢、插入、更新、刪除等。通過SqlSessionFactory建立SqlSession,而SqlSessionFactory是通過SqlSessionFactoryBuilder進行建立的。
SqlSession是一個面向程式設計師的介面,SqlSession中定義了資料庫操作方法,所以SqlSession作用是操作資料庫,並且SqlSession物件要儲存資料庫連線、事務和一級快取結構等。
每個執行緒都應該有它自己的SqlSession例項。SqlSession的例項不能共享使用,它是執行緒不安全的(多執行緒訪問系統,當多執行緒同時使用一個SqlSession物件時會造成資料衝突問題)。由於SqlSession物件是執行緒不安全的,因此它的最佳使用範圍是請求或方法範圍(也可說為SqlSession的最佳使用場合是在方法體內作為區域性變數來使用),絕對不能將SqlSession例項的引用放在一個類的靜態欄位或例項欄位中。
開啟一個SqlSession,使用完畢就要關閉它。通常把這個關閉操作放到finally塊中以確保每次都能執行關閉。如下:

SqlSession session = sqlSessionFactory.openSession();
try {
    // do work
} finally {
    session.close();
}

原始Dao開發方式

原始Dao開發方法需要程式設計師自己編寫Dao介面和Dao實現類。
在工程的src目錄下建立一個cn.itheima.mybatis.dao包,並在該包下編寫一個UserDao介面:

public interface UserDao {

    User getUserById(int id);
    List<User> getUserByName(String username);
    void insertUser(User user);

}

接著再在src目錄下建立一個cn.itheima.mybatis.dao.impl包,在該包下編寫UserDao介面的實現類——UserDaoImpl.java:

public class UserDaoImpl implements UserDao {

    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public User getUserById(int id) {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 根據id查詢使用者資訊
        User user = sqlSession.selectOne("getUserById", id);
        // 關閉sqlSession
        sqlSession.close();
        return user;
    }

    @Override
    public List<User> getUserByName(String username) {
        // 建立一個SQLSession物件
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 執行查詢
        List<User> list = sqlSession.selectList("getUserByName", username);
        // 釋放資源
        sqlSession.close();
        return list;
    }

    @Override
    public void insertUser(User user) {
        // 建立一個SQLSession物件
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 插入使用者
        sqlSession.insert("insertUser", user);
        // 提交事務
        sqlSession.commit();
        // 釋放資源
        sqlSession.close();
    }

}

最後建立一個JUnit的測試類——UserDaoTest.java,對UserDao介面進行測試。步驟如下:

  1. 在工程下新建一個原始碼目錄,專門用於存放JUnit的單元測試類。
  2. 右鍵【UserDao.java】→【New】→【JUnit Test Case】
    這裡寫圖片描述
  3. 在彈出的對話方塊中,修改單元測試類的存放目錄為test原始碼目錄
    這裡寫圖片描述
  4. 點選【Next】按鈕,在彈出的對話方塊中勾選全部測試方法,然後點選【Finish】完成
    這裡寫圖片描述

這樣就會產生如下效果:
這裡寫圖片描述
最後將JUnit的單元測試類——UserDaoTest.java修改為:

public class UserDaoTest {

    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 testGetUserById() {
        UserDao userDao = new UserDaoImpl(sqlSessionFactory);
        User user = userDao.getUserById(10);
        System.out.println(user);
    }

    @Test
    public void testGetUserByName() {
        UserDao userDao = new UserDaoImpl(sqlSessionFactory);
        List<User> list = userDao.getUserByName("張");
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testInsertUser() {
        UserDao userDao = new UserDaoImpl(sqlSessionFactory);
        User user = new User();
        user.setUsername("趙雲");
        user.setAddress("正定");
        user.setBirthday(new Date());
        user.setSex("1");
        userDao.insertUser(user);
    }

}

讀者可自行測試,在此不做過多贅述。

原始Dao開發方式所帶來的問題

從以上UserDaoImpl類的程式碼可看出原始Dao開發存在以下問題:

  1. dao介面實現類方法中存在大量的重複程式碼,這些重複的程式碼就是模板程式碼。
    模板程式碼為:

    • 先建立sqlsession
    • 再呼叫sqlsession的方法
    • 再提交sqlsession
    • 再關閉sqlsession

    設想能否將這些程式碼提取出來,這可大大減輕程式設計師的工作量。

  2. 呼叫sqlSession的資料庫操作方法需要指定statement的id,這裡存在硬編碼,不得於開發維護。
  3. 呼叫sqlsession方法時傳入的變數,由於sqlsession方法使用泛型,即使變數型別傳入錯誤,在編譯階段也不報錯,不利於程式設計師開發。

下面我來使用mapper代理方法來開發Dao層,來解決上面我們所發現的問題。

Mapper動態代理開發方式

開發規範

Mapper介面開發方法只需要程式設計師編寫Mapper介面(相當於Dao介面),由Mybatis框架根據介面定義建立介面的動態代理物件,代理物件的方法體同上邊Dao介面實現類方法。
Mapper介面開發需要遵循以下規範:

  1. Mapper.xml檔案中的namespace與mapper介面的類路徑相同,即namespace必須是介面的全限定名。
  2. Mapper介面方法名和Mapper.xml中定義的每個statement的id相同。
  3. Mapper介面方法的輸入引數型別和mapper.xml中定義的每個sql的parameterType的型別相同。
  4. Mapper介面方法的輸出引數型別和mapper.xml中定義的每個sql的resultType的型別相同。

接下來我就來編碼使用mapper動態代理方式來開發Dao層。

編寫Mapper.xml(對映檔案)

我們可在config原始碼目錄下新建一個mapper的普通資料夾,該資料夾專門用於存放對映檔案。然後在該資料夾下建立一個名為mapper.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="cn.itheima.mybatis.mapper.UserMapper">
    <select id="getUserById" parameterType="int" resultType="USer">
        select * from user where id=#{id};
    </select>

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

    <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>
</mapper>

編寫Mapper介面

在工程的src目錄下新建一個cn.itheima.mybatis.mapper包,並在該包下建立一個Mapper介面——UserMapper.java:

public interface UserMapper {

    User getUserById(int id);
    List<User> getUserByName(String username);
    void insertUser(User user);

}

介面定義有如下特點:

  1. mapper介面方法名和mapper.xml中定義的statement的id相同。
  2. mapper介面方法的輸入引數型別和mapper.xml中定義的statement的parameterType的型別相同。
  3. mapper介面方法的輸出引數型別和mapper.xml中定義的statement的resultType的型別相同。

載入mapper.xml對映檔案

在SqlMapConfig.xml檔案新增如下配置:

<mapper resource="mapper/mapper.xml"/>

如此一來,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" />
            <!-- 資料庫連線池 -->
            <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>

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

編寫測試程式

最後編寫UserMapper介面的一個單元測試類,具體步驟我已在本文中詳細說明了。修改UserMapperTest這個單元測試類的內容為:

public class UserMapperTest {

    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 testGetUserById() {
        // 和Spring整合後就省略了
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 獲得代理物件(到時候就只需要通過Spring容器拿到UserMapper介面的代理物件就可以了)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.getUserById(10);
        System.out.println(user);

        // 和Spring整合後就省略了
        sqlSession.close();
    }

    @Test
    public void testGetUserByName() {
        // 和Spring整合後就省略了
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 獲得代理物件(到時候就只需要通過Spring容器拿到UserMapper介面的代理物件就可以了)
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> list = userMapper.getUserByName("張");
        for (User user : list) {
            System.out.println(user);
        }

        // 和Spring整合後就省略了
        sqlSession.close();
    }

    @Test
    public void testInsertUser() {
        // 讀者自己編寫程式碼測試......
    }

}

以上方法測試均沒問題,讀者如若不信可親測。

小結

selectOne和selectList

動態代理物件呼叫sqlSession.selectOne()和sqlSession.selectList()是根據mapper介面方法的返回值決定,如果返回list則呼叫selectList方法,如果返回單個物件則呼叫selectOne方法。

namespace

mybatis官方推薦使用mapper代理方法開發mapper介面,程式設計師不用編寫mapper介面實現類,使用mapper代理方法時,輸入引數可以使用pojo包裝物件或map物件,保證dao的通用性。

SqlMapConfig.xml配置檔案

配置內容

SqlMapConfig.xml檔案中配置的內容和順序如下:

  1. properties(屬性)
  2. settings(全域性配置引數)
  3. typeAliases(類型別名)
  4. typeHandlers(型別處理器)
  5. objectFactory(物件工廠)
  6. plugins(外掛)
  7. environments(環境集合屬性物件)
    • environment(環境子屬性物件)
      • transactionManager(事務管理)
      • dataSource(資料來源)
  8. mappers(對映器)

properties(屬性)

在SqlMapConfig.xml配置檔案中,我們可把資料庫連線資訊配置到properties標籤當中,類似如下:

<!-- 配置屬性 -->
<properties>
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
    <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
</properties>

接下來把以上配置資訊新增到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>
    <!-- 配置屬性 -->
    <properties>
        <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
    </properties>
    <!-- 和spring整合後 environments配置將廢除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理 -->
            <transactionManager type="JDBC" />
            <!-- 資料庫連線池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="root" />
                <property name="password" value="yezi" />
            </dataSource>
        </environment>
    </environments>

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

SqlMapConfig.xml檔案向上面這樣配置,雖然沒問題,但是我們不覺得這樣很不爽嗎?我們一般都是將資料庫連線資訊配置到一個java屬性檔案中,然後再來引用其中的配置資訊。我按照這種指導思想在classpath下定義一個db.properties檔案,SqlMapConfig.xml檔案引用該屬性檔案中的配置資訊如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=yezi

如此一來,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>
    <!-- 配置屬性 -->
    <properties resource="db.properties">
        <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
        <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
    </properties>
    <!-- 和spring整合後 environments配置將廢除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理 -->
            <transactionManager type="JDBC" />
            <!-- 資料庫連線池 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

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

注意:MyBatis將按照下面的順序來載入屬性:

  1. 在properties元素體內定義的屬性首先被讀取
  2. 然後會讀取properties元素中resource或url載入的屬性,它會覆蓋已讀取的同名屬性

按照我自己的話說就是:先載入property元素內部的屬性,然後再載入db.properties檔案外部的屬性,如果有同名屬性則會覆蓋。

如若讀者不信,可以將properties元素的內容修改為:

<properties resource="db.properties">
    <property name="jdbc.driver" value="com.mysql.jdbc.Driver"/>
    <property name="jdbc.url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
    <property name="jdbc.username" value="hello"/>
</properties>

以上故意將資料庫連線的使用者名稱給整錯,結果發現仍然好使,這足以說明問題了。

typeAliases(類型別名)

mybatis支援別名

mybatis支援別名如下表:

別名 對映的型別
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
map Map

自定義別名

如果我們在SqlMapConfig.xml配置檔案新增如下配置資訊:

<!-- 配置pojo的別名 -->
<typeAliases>
    <!-- 單個別名定義 -->
    <typeAlias type="cn.itheima.mybatis.po.User" alias="user"/>
</typeAliases>

以上就為User類定義了一個別名(user)。
那麼我們就可以在mapper.xml對映檔案中使用這個別名了,如下:

<mapper namespace="cn.itheima.mybatis.mapper.UserMapper">
                                                <!-- 別名不區分大小寫 -->
    <select id="getUserById" parameterType="int" resultType="USer">
        select * from user where id=#{id};
    </select>

    ......
</mapper>

注意:resultType屬性的值就是User類的別名,且別名是不區分大小寫的。
聰明的小夥伴們肯定發現如果像這樣為每一個pojo定義一個別名,並不是明智之舉,萬一一個專案裡面有很多pojo類呢?所以我們可以批量定義別名,如下:

<!-- 配置pojo的別名 -->
<typeAliases>
    <!-- 批量別名定義,掃描包的形式建立別名,別名就是類名,且不區分大小寫 -->
    <package name="cn.itheima.mybatis.po"/>
</typeAliases>

SqlMapConfig.xml檔案載入mapper.xml檔案

Mapper(對映器)配置的幾種方法:

  1. <mapper resource=" " />
    使用相對於類路徑的資源,如

    <mapper resource="sqlmap/user.xml"/>

    這裡寫圖片描述

  2. <mapper class=" " />
    使用mapper介面類路徑,如:

    <mapper class="cn.itheima.mybatis.mapper.UserMapper"/>

    這裡寫圖片描述
    注意:此種方法要求mapper介面名稱和mapper對映檔名稱相同,且放在同一個目錄中。

  3. <package name=""/>
    註冊指定包下的所有mapper介面,如:

    <package name="cn.itheima.mybatis.mapper"/>

    這裡寫圖片描述
    注意:此種方法要求mapper介面名稱和mapper對映檔名稱相同,且放在同一個目錄中。

雖然Mapper(對映器)配置有以上三種方法,但是實際開發中就用第三種方法。至此,使用MyBatis開發Dao層我就總結到這裡。