1. 程式人生 > >從零開始認識並操縱Mybatis

從零開始認識並操縱Mybatis

目錄

@(認識並操縱Mybatis)

宣告:從零開始,並不代表你對java Mybatis一點都不懂的程度哈,本例項只是過一個概貌,詳細內容會分多篇文章拆解

業務介紹

使用者模組的管理,使用者表的維護:

  1. 新增使用者
  2. 修改使用者資訊
  3. 刪除使用者
  4. 查詢使用者
    1. id查詢單個
    2. 使用者名稱模糊查詢
    3. 查詢所有
    4. 查詢總數
    5. 根據ids查詢
    6. 多條件查詢

使用者和訂單關聯管理

  1. 根據訂單查詢對應使用者資訊
  2. 根據使用者查詢所有訂單資訊

版本宣告

只使用Mybatis,不整合其他框架。Mybatis版本為^3.4

開發工具使用eclipse 使用的是純 java Project

整個流程的詳細專案程式碼可以踩我的github結合觀光:https://github.com/Autom-liu/MybatisLearn

每一個步驟都分得很清晰哦,可以clone下來再研究

認識Mybatis

是什麼

MyBatis是一個優秀的持久層框架,不算完整的ORM框架

歷史

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

傳統JDBC存在的問題

  • 資料庫連線建立、釋放頻繁造成系統資源浪費從而影響系統性能,如果使用資料庫連線池可解決此問題。
    • Mybatis 在配置檔案中配置了資料連線池,使用連線池管理資料庫連結。
  • Sql語句寫在程式碼中造成程式碼不易維護,實際應用sql變化的可能較大,sql變動需要改變java程式碼。
    • Mybatis 將Sql語句配置在配置檔案中與java程式碼分離。
  • 向sql語句傳引數麻煩,因為sql語句的where條件不一定,可能多也可能少,佔位符需要和引數一一對應。
    • Mybatis自動將java物件對映至sql語句,通過statement中的parameterType定義輸入引數的型別。
  • 對結果集解析麻煩,sql變化導致解析程式碼變化,且解析前需要遍歷,如果能將資料庫記錄封裝成pojo物件解析比較方便。
    • Mybatis自動將sql執行結果對映至java物件,通過statement中的resultType定義輸出結果的型別。

與完整的ORM框架Hibernate的區別

  • Mybatis不完全是一個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架構

image.png

入門程式操作步驟

下載和導包

下載連結:https://github.com/mybatis/mybatis-3/releases

目錄結構:

image.png

導包:

  • Mybatis核心包
  • Mybatis依賴包
  • 資料庫驅動包 注:這裡使用的是老版本的5.1的包,新版本有很大改動!

準備資料庫和Java Bean

兩張表: 使用者表 + 訂單表 基本建立即可,無其他關聯

image.png

image.png

準備javaBean 就不多說了,要注意的一點就是,javaBean最好實現
Serializable序列化介面持久化,還有一點就是命名規範吧,不同人說法不同,有些是直接乾脆和資料庫欄位名一樣,而這裡不那麼一致,使用程式語言中統一的駝峰命名法,而不是資料庫中的短劃線分割。

要相信的是,任何一個框架總會幫我們解決這個對映問題的!

配置Mybatis

在官方給出的文件當中給出了我們這樣的配置:

官方的檔名是: mybatis-config.xml 當然這並不是定死的。

<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>

這個是主配置檔案,也就是架構當中的最頂層,基本上參照這個配置copy,稍作修改即可,就不再匯入約束啥的了。

比如在本次配置裡,建立的是src/sqlMap.xml,檔名不是固定的,後邊程式會指定

還有一點就是:不用太在意每個配置項的含義,大概看得懂就行,因為後期往往需要整合spring,這些配置全部不用了,作為入門程式而已,給大家觀光一下

<!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="****"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="sqlMap/User.xml"/>
    </mappers>
</configuration>

編寫sql對映檔案

這裡可以看到Mapper標籤,是引入其他對映檔案,這個檔案就是架構當中的第二層,多個mapper的組成,每個mapper都是若干個sql語句

到這裡就需要考慮實現業務的sql語句應該如何書寫了,這裡先只貼出增和查的業務,剩下都以舉例完成

sqlMap/User.xml

<mapper namespace="user">
    <select id="findUser" resultType="edu.scnu.bean.User" parameterType="Integer">
        select * from mb_user where id = #{id}
    </select>
    <select id="getByUsername" resultType="edu.scnu.bean.User" parameterType="String">
        select * from mb_user where username like "%"#{username}"%"
    </select>
    <insert id="save" parameterType="edu.scnu.bean.User">
        insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
    </insert>
</mapper>

namespace 是名稱空間,這個真正的妙用後面體現出來,這裡暫時只需要知道用於區分同名作用域即可

id 可以認為是該條sql語句的識別符號

parameterType 傳遞進來的引數型別,可以看到基本資料型別可以省略,而其他的就要完整類名了

resultType 返回值型別,同上一樣

select 標籤用於書寫select查詢語句

insert 標籤用於書寫insert插入語句 update delete一樣

嘗試了一下 select 書寫insert好像也可以,但不推薦

關於模糊查詢的補充

在Mybatis中使用 #{} 作為佔位符,佔位符的作用在於會在識別符號取得結果後前後加單引號。

如果直接使用:

select * from mb_user where username like "%#{username}%"

就不對了,因為假如username=admin 結果變為:

select * from mb_user where username like "%'admin'%"

就語法錯誤或沒有結果

可以使用 ${} 這種字串拼接的方式

select * from mb_user where username like "%${username}%"

不過這樣只是沒有了佔位符引數的效果了而已。

書寫Dao類

接下來就到來書寫真正的Dao類了,這裡還是按照規範實現介面

public interface UserDao {
    void save(User user);
    void update(User user);
    void delete(Integer id);
    User find(Integer id);
//  List<User> getAll();
//  Integer getCount();
    List<User> getByUsername(String username);
//  List<User> getByCond(User user);
}

看下實現類:

public class UserDaoImpl implements UserDao {

    private SqlSessionFactory sqlSessionFactory;

    // 沒有spring幫助下,就需要外部手動注入!
    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public void save(User user) {
        SqlSession session = sqlSessionFactory.openSession();
        session.insert("user.save", user);
        session.commit();
    }

    @Override
    public User find(Integer id) {
        SqlSession session = sqlSessionFactory.openSession();
        return session.selectOne("user.findUser", id);
    }

    @Override
    public List<User> getByUsername(String username) {
        SqlSession session = sqlSessionFactory.openSession();
        return session.selectList("user.getByUsername", username);
    }

}

測試類

最後即可編寫測試了

public class MybatisTest {
    
    private SqlSessionFactory sqlSessionFactory;
    private UserDao userDao;
    
    @Before
    public void before() throws Exception{
        String resource = "sqlMap.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        userDao = new UserDaoImpl(sqlSessionFactory);
    }
    
    @Test
    public void testFind() {
        userDao.find(1);
    }
    
    @Test
    public void testGetByUsername() {
        userDao.getByUsername("王");
    }
    
    @Test
    public void testSave() {
        userDao.save(new User("未知使用者", "2", new Date(), "中國"));
    }
}

返回id 的save

現在對實現增加使用者新增需求,就是在新增使用者的同時根據該使用者ID查詢該訂單資訊

這時候我們首先要獲取新增使用者後的id,但是新增使用者是資料庫給我們返回的是受影響條數,如何快速獲得最新插入資料的id呢

sql語法提供了 select LAST_INSERT_ID()供我們獲取最新插入資料的ID,我們可以將該sql嵌入到sql配置檔案中去

這裡簡單講解配置語法:

  • selectKey 標籤實現主鍵返回
  • keyColumn:主鍵對應的表中的哪一列
  • keyProperty:主鍵對應的pojo(Bean)中的哪一個屬性
  • order:設定在執行insert語句前執行查詢id的sql,還是在執行insert語句之後執行查詢id的sql

使用主鍵自增策略的id:

<!-- 生成主鍵的順序Mysql是之後,oracle是之前 -->
<selectKey keyProperty="id" resultType="Integer" order="AFTER">
    select LAST_INSERT_ID()
</selectKey>

使用uuid的String

<selectKey keyColumn="id" keyProperty="id" order="BEFORE" resultType="string">
    select LAST_INSERT_ID()
</selectKey>

這裡可以修改上述插入SQL語法配置:

<insert id="save" parameterType="edu.scnu.bean.User">
    <!-- 生成主鍵的順序Mysql是之後,oracle是之前 -->
    <selectKey keyProperty="id" resultType="Integer" order="AFTER">
        select LAST_INSERT_ID()
    </selectKey>
    insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
</insert>

原始Dao開發方式存在的問題

  • Dao方法體存在重複程式碼:通過SqlSessionFactory建立SqlSession,呼叫SqlSession的資料庫操作方法
  • 呼叫sqlSession的資料庫操作方法需要指定statement的id,這裡存在硬編碼,不得於開發維護。

上述問題主要關鍵還是在於同類型的枯燥性重複性程式碼,只有少數引數傳遞部分要變動,其它基本都是同樣的套路,因此可不可以有個辦法一些重複性工作呢?Mybatis貼心為我們服務了這一點,它省去的不僅僅是一兩條語句,它把整個實現類都為我們省去了。來看看它是怎麼工作的吧!

Mapper 動態代理方式

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

Mapper介面開發需要遵循以下規範:
1、 Mapper.xml檔案中的namespace與mapper介面的類路徑相同。
2、 Mapper介面方法名和Mapper.xml中定義的每個statement的id相同
3、 Mapper介面方法的輸入引數型別和mapper.xml中定義的每個sql 的parameterType的型別相同
4、 Mapper介面方法的輸出引數型別和mapper.xml中定義的每個sql的resultType的型別相同

也就是說,我們要完成的只是介面,實現類框架幫我們搞定了...

那麼首先來修改優化一下配置檔案

<!-- 使用動態代理開發DAO,1. namespace必須和Mapper介面類路徑一致 -->
<mapper namespace="edu.scnu.dao.UserDao">
    <!-- 根據使用者id查詢使用者 -->
    <!-- 2. id必須和Mapper介面方法名一致 -->
    <!-- 3. parameterType必須和介面方法引數型別一致 -->
    <!-- 4. resultType必須和介面方法返回值型別一致 -->
    <select id="find" resultType="edu.scnu.bean.User" parameterType="Integer">
        select * from mb_user where id = #{id}
    </select>
    <select id="getByUsername" resultType="edu.scnu.bean.User" parameterType="String">
        select * from mb_user where username like "%"#{username}"%"
    </select>
    <insert id="save" parameterType="edu.scnu.bean.User">
        <!-- 生成主鍵的順序Mysql是之後,oracle是之前 -->
        <selectKey keyProperty="id" resultType="Integer" order="AFTER">
            select LAST_INSERT_ID()
        </selectKey>
        insert into mb_user (username,birthday,sex,address) values (#{username}, #{birthday}, #{sex}, #{address})
    </insert>
</mapper>

接下來如果把實現類刪除了,我們發現測試類例項化就報錯了,這時候我們需要通過session的getMapper方法獲得Mybatis幫我們建立好的實現類

測試類:

@Before
public void before() throws Exception{
    String resource = "sqlMapConfig.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession session = sqlSessionFactory.openSession();
    userDao = session.getMapper(UserDao.class);
}

優化配置檔案

到目前為止,我們書寫了兩個配置檔案,這兩個配置檔案開始出現不僅繁雜不好維護的問題,還有挺多不規範的地方:

  1. 資料庫連線引數不應該直接在xml配置當中,應該提取出來:

使用property屬性

<!-- 是用resource屬性載入外部配置檔案 -->
<properties resource="db.properties">
    <!-- 在properties內部用property定義屬性 -->
    <!-- 如果外部配置檔案有該屬性,則內部定義屬性被外部屬性覆蓋 -->
    <property name="jdbc.password" value="root" />
</properties>
<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>
  1. 類名過長重複

使用typeAliases屬性自定義別名

mybatis已經為我們提供了很多基本資料型別以及包裝類的別名,同樣,我們也可以自己自定義類名別名

支援單個別名的定義

<typeAliases>
    <!-- 單個別名定義 -->
<typeAlias alias="user" type="edu.scnu.bean.User" />
</typeAliases>

還有批量定義,這裡選用批量定義的方式

<typeAliases>
    <!-- 批量別名定義,掃描整個包下的類,別名為類名(大小寫不敏感) -->
    <package name="edu.scnu.bean" />
</typeAliases>

這樣後續的所有ResultType 和 parameterType只需要直接寫User或user即可

詭異的是,也是需要注意的是順序問題

typeAliases 必須緊跟 property 後

關於Mappers對映器屬性

resource

  • 使用相對於類路徑的資源(現在的使用方式)
    • 如:

class

  • 使用mapper介面類路徑
    • 如:
    • 注意:此種方法要求mapper介面名稱和mapper對映檔名稱相同,且放在同一個目錄中。

  • 註冊指定包下的所有mapper介面
    • 如:
    • 注意:此種方法要求mapper介面名稱和mapper對映檔名稱相同,且放在同一個目錄中。
    • 用於批量引入

輸入型別和輸出型別

接下來我們來完善其他業務功能,這裡為了演示以下各類幾種輸入和輸出型別的情況,我們增加一個實體型別包裝實體

輸入型別:

  • 基本型別
  • 實體型別
  • 實體型別包裝類

說白了傳遞引用型別,會自動把所有引用屬性轉化為map物件,這點和struts的OGNL表示式類似

輸出型別:

  • 基本型別
  • 實體
  • 實體列表
    • 只需要傳遞泛型型別(集合元素的型別即可)

然後來完善我們其他功能

parameterType為實體包裝類的情況

<update id="update" parameterType="UserWrapper">
    update mb_user set username=#{user.username}, birthday=#{user.birthday}, sex=#{user.sex}, address=#{user.address} where id=#{user.id}
</update>

resultType為基本型別的情況

<select id="getCount" resultType="int">
    select count(*) from mb_user
</select>

動態sql完成多條件查詢

使用where標籤和if標籤

<select id="getByCond" parameterType="User" resultType="User">
    select * from mb_user
    <where>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="username != null and username != ''">
            and username = #{username}
        </if>
        <if test="address != null and address != ''">
            and address = #{address}
        </if>
    </where>
</select>

使用sql片段簡化

sql標籤宣告 include引入

<sql id="selectSql">
    select * from ub_user
</sql>
<select id="find" resultType="User" parameterType="Integer">
    <include refid="selectSql" /> where id = #{id}
</select>

使用foreach標籤實現多id查詢

原理

select * from mb_user where id in (1,2,3);

以java陣列傳遞引數

<select id="getByIds" parameterType="Integer" resultType="User">
    <include refid="selectSql" />
    <where>
        <foreach collection="array" item ="id" separator="," open="id in (" close=")">
            #{id}
        </foreach>
    </where>
</select>

以list集合傳遞引數

<select id="getByIds" parameterType="Integer" resultType="User">
    <include refid="selectSql" />
    <where>
        <foreach collection="list" item ="id" separator="," open="id in (" close=")">
            #{id}
        </foreach>
    </where>
</select>

resultMap 輸出對映,完成訂單資訊

之前我們說到,訂單Dao類是以駝峰命名的,而資料庫欄位是下劃線命名的

在之前同名的情況都可以自動對映,那麼針對這種不同名的情況又如何呢?

我們先簡單完成以下訂單的其中一個業務

public interface OrdersDao {
    List<Orders> getAll();
}

使用者訂單關聯查詢

一對一

方式一、使用新建實體類繼承關係

新建一個OrdersUser 的javaBean類

public class OrdersUser extends Orders {
    private String username;
    private String address;
    // ...
}

配置OrdersUser.xml

<mapper namespace="edu.scnu.dao.OrdersUserDao">
    <select id="queryAll" resultType="OrdersUser">
        SELECT
            o.id,
            o.user_id
            userId,
            o.number,
            o.createtime,
            o.note,
            u.username,
            u.address
        FROM
            `order` o
        LEFT JOIN `user` u ON o.user_id = u.id

    </select>

</mapper>

方式二、直接多對一關係對映

直接在Orders實體類下增加User欄位,並在配置檔案裡作如下對映:

<resultMap type="Orders" id="orderResultMap">
    <!-- 定義主鍵 ,非常重要。如果是多個欄位,則定義多個id -->
    <id property="id" column="id"></id>
    <result property="userId" column="user_id"/>
    <!-- 關聯,屬性全寫上 -->
    <result property="number" column="number" />
    <result property="createtime" column="createtime" />
    <result property="note" column="note" />
    
    
    <association property="user" javaType="User">
        <!-- id:宣告主鍵,表示user_id是關聯查詢物件的唯一標識-->
        <id property="id" column="user_id" />
        <result property="username" column="username" />
        <result property="address" column="address" />
    </association>
</resultMap>

一對多關係

一對多關係只能使用resultMap的collection對映

同樣在User Bean類中新增List<Orders>欄位

private List<Orders> orders;

<resultMap type="User" id="UserOrdersResultMap">
    <id property="id" column="id" />
    <result property="username" column="username"/>
    <result property="address" column="address"/>
    <collection property="orders" javaType="List" ofType="Orders">
        <result property="id" column="oid"/>
        <result property="createtime" column="createtime"/>
        <result property="number" column="number"/>
    </collection>
</resultMap>
<select id="getWithOrders" resultMap="UserOrdersResultMap">
    SELECT
        u.id,
        u.username,
        u.address,
        o.id oid,
        o.number,
        o.createtime
    FROM
        `mb_user` u
    LEFT JOIN `mb_orders` o ON u.id = o.user_id
</select>

結束

好了,到這裡已經介紹了單獨使用Mybatis操縱的所有功能,整個流程到這裡就算完成了,整個流程的詳細專案程式碼可以踩我的github結合觀光:https://github.com/Autom-liu/MybatisLearn ,整個流程涉及到諸多知識細節需要慢慢琢磨,這裡只給大家展示整個概貌,具體細節會在後續的文章中慢慢解釋,還會給大家展示Mybatis和Spring整合後的精彩內容哦,期待大家的支援和star~~~