MyBatis學習(二)

一滴白雲發表於2020-11-05

一、MyBatis執行SQL的兩種方式:SqlSession和Mapper介面

1. 用 Mapper 介面傳送 SQL

 PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
  personMapper.insertPerson(person);

  通過 SqlSession 的 getMapper 方法來獲取一個 Mapper 介面,就可以呼叫它的方法了。因為 SQL對映 檔案或者介面註解定義的 SQL 都可以通過“類的全限定名+方法名”查詢,所以 MyBatis 會啟用對應的 SQL 進行執行,並返回結果。
例項:

package com.wangxing.mybatis.test;
import com.wangxing.mybatis.bean.Person;
import com.wangxing.mybatis.mapper.PersonMapper;
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 java.util.List;

public class TestMain {
    public static void testInsertPerson(){
        //定義SqlSession物件
        SqlSession sqlSession=null;
        try {
            //通過SqlSessionFactoryBuilder類建立出SqlSessionFactory物件
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            //從SqlSessionFactory中取得一個SqlSession物件
            sqlSession=sqlSessionFactory.openSession();
            //通過得到資料訪問介面物件呼叫insertPerson方法
            PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
            Person person=new Person();
            person.setPername("zhangsan");
            person.setPerage(23);
            person.setPeraddress("西安");
            personMapper.insertPerson(person);
            //提交sqlsession
            sqlSession.commit();
        }catch (Exception e){
           e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }

    public static void testUpdatePerson(){
        //定義SqlSession物件
        SqlSession sqlSession=null;
        try {
            //通過SqlSessionFactoryBuilder類建立出SqlSessionFactory物件
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            //從SqlSessionFactory中取得一個SqlSession物件
            sqlSession=sqlSessionFactory.openSession();
            //通過得到資料訪問介面物件呼叫insertPerson方法
            PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
            Person person=new Person();
            person.setPerid(1);
            person.setPername("lisi");
            person.setPerage(24);
            person.setPeraddress("beijing");
            personMapper.updatePerson(person);
            //提交sqlsession
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }

    public static void testSelectPersonById(){
        //定義SqlSession物件
        SqlSession sqlSession=null;
        try {
            //通過SqlSessionFactoryBuilder類建立出SqlSessionFactory物件
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            //從SqlSessionFactory中取得一個SqlSession物件
            sqlSession=sqlSessionFactory.openSession();
            //通過得到資料訪問介面物件呼叫insertPerson方法
            PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
            Person person=personMapper.selectPersonById(1);
            //提交sqlsession
            sqlSession.commit();
            System.out.println(person.getPerid()+"\t"+person.getPername());
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }

    public static void testSelectPerson(){
        //定義SqlSession物件
        SqlSession sqlSession=null;
        try {
            //通過SqlSessionFactoryBuilder類建立出SqlSessionFactory物件
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            //從SqlSessionFactory中取得一個SqlSession物件
            sqlSession=sqlSessionFactory.openSession();
            //通過得到資料訪問介面物件呼叫insertPerson方法
            PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
            List<Person> personlist=personMapper.selectPerson();
            //提交sqlsession
            sqlSession.commit();
            System.out.println("personlist.size----"+personlist.size());
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }

    public static void testDeletePerson(){
        //定義SqlSession物件
        SqlSession sqlSession=null;
        try {
            //通過SqlSessionFactoryBuilder類建立出SqlSessionFactory物件
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            //從SqlSessionFactory中取得一個SqlSession物件
            sqlSession=sqlSessionFactory.openSession();
            //通過得到資料訪問介面物件呼叫insertPerson方法
            PersonMapper personMapper=sqlSession.getMapper(PersonMapper.class);
            personMapper.deletePersonById(1);
            //提交sqlsession
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }
    public static void main(String[] args) {
        //測試新增使用者資訊
        //testInsertPerson();
        //測試修改使用者資訊
        //testUpdatePerson();
        //測試根據id查詢使用者資訊
        //testSelectPersonById();
        //測試查詢所有使用者資訊
        //testSelectPerson();
        //測試根據id刪除使用者資訊
        testDeletePerson();
    }
}

2.SqlSession 傳送 SQL

  通過SqlSession物件的

int  insert(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
int  update(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
int  delete(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
<T>  selectOne(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
List<T>  selectList(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
.......

  例項:

package com.wangxing.mybatis.test;
import com.wangxing.mybatis.bean.Person;
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 java.util.List;

public class TestMain {

    public static void testInsertPerson(){
        SqlSession sqlSession=null;
        try{
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession=sqlSessionFactory.openSession();
            Person person=new Person();
            person.setPername("zhangsan");
            person.setPerage(23);
            person.setPeraddress("xian");
        sqlSession.insert("com.wangxing.mybatis.mapper.PersonMapper.insertPerson",person);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    public static void testUpdatePerson(){
        SqlSession sqlSession=null;
        try{
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession=sqlSessionFactory.openSession();
            Person person=new Person();
            person.setPerid(1);
            person.setPername("zhangsanfeng");
            person.setPerage(123);
            person.setPeraddress("wudang");
            sqlSession.update("com.wangxing.mybatis.mapper.PersonMapper.updatePerson",person);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    public static void testSelectPersonById(){
        SqlSession sqlSession=null;
        try{
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession=sqlSessionFactory.openSession();
            Person person=(Person)sqlSession.selectOne("com.wangxing.mybatis.mapper.PersonMapper.selectPersonById",1);
            sqlSession.commit();
            System.out.println(person.getPerid()+"\t"+person.getPername());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    public static void testSelectPerson(){
        SqlSession sqlSession=null;
        try{
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession=sqlSessionFactory.openSession();
            List<Person> perlist=sqlSession.selectList("com.wangxing.mybatis.mapper.PersonMapper.selectPerson");
            sqlSession.commit();
            System.out.println("perlist.size=="+perlist.size());
            Person person=perlist.get(0);
            System.out.println(person.getPerid()+"\t"+person.getPername());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }

    public static void testDeletePerson(){
        SqlSession sqlSession=null;
        try{
            SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession=sqlSessionFactory.openSession();
            sqlSession.delete("com.wangxing.mybatis.mapper.PersonMapper.deletePersonById",1);
            sqlSession.commit();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }
    public static void main(String[] args) {
        //測試新增使用者資訊
        //testInsertPerson();
        //測試修改使用者資訊
        //testUpdatePerson();
        //測試根據id查詢使用者資訊
        //testSelectPersonById();
        //測試查詢所有使用者資訊
        //testSelectPerson();
        //測試根據id刪除使用者資訊
        testDeletePerson();
    }
}

3.對比兩種傳送 SQL 方式

  上面分別展示了 MyBatis 存在的兩種傳送 SQL 的方式,一種用 SqlSession 直接傳送,另外一種通過 SqlSession 獲取 Mapper 介面再傳送。建議採用 SqlSession 獲取 Mapper 的方式,理由如下:

  (1) 使用 Mapper 介面程式設計可以消除 SqlSession 帶來的功能性程式碼,提高可讀性,而 SqlSession 傳送 SQL,需要一個 SQL id 去匹配 SQL,比較晦澀難懂。
  (2) 使用Mapper.selectPersonById(1)方式,IDE會提示錯誤和校驗,而使用 sqlSession.selectOne(“com.wangxing.mybatis.mapper.PersonMapper.selectPersonById”,1)語法,只有在執行中才能知道是否會產生錯誤。
  目前使用Mapper介面程式設計已成為主流,尤其在Spring 中運用MyBatis 時,Mapper 介面的使用就更為簡單,所以本教程使用Mapper 介面傳送SQL語句並執行的方式。

二、MyBatis 的工作原理

在這裡插入圖片描述

  下面對圖中的每步流程進行說明。
  1)讀取 MyBatis 配置檔案:mybatis-config.xml 為 MyBatis 的全域性配置檔案,配置了 MyBatis 的執行環境等資訊,例如資料庫連線資訊。
  2)載入SQL對映檔案。對映檔案即 SQL 對映檔案,該檔案中配置了運算元據庫的 SQL 語句,需要在 MyBatis 配置檔案 mybatis-config.xml 中載入。mybatis-config.xml 檔案可以載入多個對映檔案,每個檔案對應資料庫中的一張表。
  3)構造會話工廠:通過 MyBatis 的環境等配置資訊構建會話工廠 SqlSessionFactory。
  4)建立會話物件:由會話工廠建立 SqlSession 物件,該物件中包含了執行 SQL 語句的所有方法。
  5)Executor 執行器:MyBatis 底層定義了一個 Executor 介面來運算元據庫,它將根據 SqlSession 傳遞的引數動態地生成需要執行的 SQL 語句,同時負責查詢快取的維護。
  6)MappedStatement 物件:在 Executor 介面的執行方法中有一個 MappedStatement 型別的引數,該引數是對對映資訊的封裝,用於儲存要對映的 SQL 語句的 id、引數等資訊。
  7)輸入引數對映:輸入引數型別可以是 Map、List 等集合型別,也可以是基本資料型別和 POJO 型別。輸入引數對映過程類似於 JDBC 對 preparedStatement 物件設定引數的過程。
  8)輸出結果對映:輸出結果型別可以是 Map、 List 等集合型別,也可以是基本資料型別和 POJO 型別。輸出結果對映過程類似於 JDBC 對結果集的解析過程。

  注意:Executor 執行器執行SQL語句時,是根據SQL對映檔案中對應元素的<insert><update><delete><select>的id屬性值去選擇對應的sql語句,由於SQL對映檔案中對應元素的<insert><update><delete><select>的id屬性值是資料訪問介面的方法名,所以資料訪問介面中的方法是不能過載的。

三、MyBatis的核心元件:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession和SQL Mapper

(1)SqlSessionFactoryBuilder(構造器)

  SqlSessionFactoryBuilder(構造器):它會根據MyBati核心配置或者程式碼來生成 SqlSessionFactory,採用的是分步構建的 Builder 模式。

  1.1.SqlSessionFactoryBuilder通過根據MyBati核心配置【mybatis-config.xml】建立SqlSessionFactory
  mybatis-config.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="mydate.properties"></properties>
    <!-- 配置mybatis預設的連線資料庫的環境 -->
    <environments default="development">
        <environment id="development">
            <!-- 配置事務管理器 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 配置資料來源 -->
            <dataSource type="POOLED">
                <property name="driver" value="${mydriver}"/>
                <property name="url" value="${myurl}"/>
                <property name="username" value="${myusername}"/>
                <property name="password" value="${mypassword}"/>
            </dataSource>
        </environment>
    </environments>
    <!-- 配置MyBatis資料訪問介面的SQL對映檔案路徑 -->
    <mappers>
        <mapper resource="PersonMapper.xml"></mapper>
    </mappers>
</configuration>

  SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(“mybatis-config.xml”));

  1.2.SqlSessionFactoryBuilder通過使用程式碼建立 SqlSessionFactory

  程式碼如下所示。

public  SqlSessionFactory   getSqlSessionFactory (){
// 資料庫連線池資訊
PooledDataSource dataSource = new PooledDataSource();
dataSource.setDriver("com.mysql.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword ("123456");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setDefeultAutoCommit(false);
// 採用 MyBatis 的 JDBC 事務方式
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment ("development", transactionFactory, dataSource);
// 建立 Configuration 物件
Configuration configuration = new Configuration(environment);
// 加入一個對映器
configuration.addMapper(RoleMapper.class);
//使用 SqlSessionFactoryBuilder 構建 SqlSessionFactory
SqlSessionFactory SqlSessionFactory =
new SqlSessionFactoryBuilder().build(configuration);
return SqlSessionFactory;
}

  注意程式碼中的註釋,它和 XML 方式實現的功能是一致的,只是方式不太一樣而已。但是程式碼冗長,如果發生系統修改,那麼有可能需要重新編譯程式碼才能繼續,所以這不是一個很好的方式,一般不推薦大家使用。除非有特殊的需要,比如在配置檔案中,需要配置加密過的資料庫使用者名稱和密碼,需要我們在生成 SqlSessionFactory 前解密為明文的時候,才會考慮使用這樣的方式。

(2)SqlSessionFactory(工廠介面)

  SqlSessionFactory(工廠介面):依靠它來生成 SqlSession,使用的是工廠模式。
  SqlSession sqlSession=SqlSessionFactory物件.openSession();

(3)SqlSession(會話)

  SqlSession(會話):一個既可以傳送 SQL 執行返回結果,也可以獲取 Mapper 的介面。在現有的技術中,一般我們會讓其在業務邏輯程式碼中“消失”,而使用的是 MyBatis 提供的 SQL Mapper 介面程式設計技術,它能提高程式碼的可讀性和可維護性。
  在 MyBatis 中有兩個實現類,DefaultSqlSession 和 SqlSessionManager。
  DefaultSqlSession 是單執行緒使用的,而 SqlSessionManager 在多執行緒環境下使用。
  傳送 SQL 執行返回結果

int  insert(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
int  update(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
int  delete(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
<T>  selectOne(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
List<T>  selectList(“資料訪問介面的包名+介面名+方法名” ,  資料訪問介面的方法的引數);
.......

  獲取 Mapper 的介面
  資料訪問介面物件=SqlSession物件.getMapper(資料訪問介面的反射物件);

(4)SQL Mapper(對映器)

  SQL Mapper(對映器):MyBatis 新設計存在的元件,它由一個 Java 介面和 XML 檔案(或註解)構成,需要給出對應的 SQL 和對映規則。它負責傳送 SQL 去執行,並返回結果。
  SQL Mapper(對映器)=資料訪問介面+SQL對映檔案/註解,負責傳送 SQL 去執行,並返回結果。

  4.1.資料訪問介面+SQL對映檔案

  資料訪問介面

package com.wangxing.mybatis.mapper;
import com.wangxing.mybatis.bean.Person;
import java.util.List;
public interface PersonMapper {
    /**
     * 新增資料
     * @param person
     * @return
     */
    boolean insertPerson(Person person);
    /**
     * 修改資料
     * @param person
     * @return
     */
    boolean updatePerson(Person person);
    /**
     * 刪除資料
     * @return
     */
    boolean deletePersonById(int perid);
    /**
     * 根據id查詢資料
     * @return
     */
    Person selectPersonById(int perid);
    /**
     * 查詢所有資料
     * @return
     */
    List<Person> selectPerson();
}

  SQL對映檔案

<?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="com.wangxing.mybatis.mapper.PersonMapper">
    <insert id="insertPerson" parameterType="com.wangxing.mybatis.bean.Person">
        insert into t_person values (null,#{pername},#{perage},#{peraddress});
    </insert>
    <update id="updatePerson" parameterType="com.wangxing.mybatis.bean.Person">
        update t_person set per_name=#{pername},per_age=#{perage},per_address=#{peraddress} where per_id=#{perid};
    </update>
    <resultMap id="personMap" type="com.wangxing.mybatis.bean.Person">
        <id column="per_id" property="perid"></id>
        <result column="per_name" property="pername"></result>
        <result column="per_age" property="perage"></result>
        <result column="per_address" property="peraddress"></result>
    </resultMap>
    <select id="selectPersonById" parameterType="int" resultMap="personMap">
        select * from  t_person where per_id=#{perid};
    </select>
    <select id="selectPerson" resultMap="personMap">
        select * from  t_person;
    </select>
    <delete id="deletePersonById" parameterType="java.lang.Integer">
        delete from t_person where per_id=#{perid};
    </delete>
</mapper>

  4.2.資料訪問介面+註解

  PersonMapper.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="com.wangxing.mybatis.mapper.PersonMapper">
    <resultMap id="perMap" type="com.wangxing.mybatis.bean.Person">
        <id column="per_id" property="perid"></id>
        <result column="per_name" property="pername"></result>
        <result column="per_age" property="perage"></result>
        <result column="per_address" property="peraddress"></result>
    </resultMap>
</mapper>

  資料訪問介面帶註解

package com.wangxing.mybatis.mapper;
import com.wangxing.mybatis.bean.Person;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface PersonMapper {
    /**
     * 新增資料
     * @param person
     * @return
     */
    @Insert("insert into t_person values (null,#{pername},#{perage},#{peraddress});")
    boolean insertPerson(Person person);
    /**
     * 修改資料
     * @param person
     * @return
     */
    @Update("update t_person set per_name=#{pername},per_age=#{perage},per_address=#{peraddress} where per_id=#{perid};")
    boolean updatePerson(Person person);
    /**
     * 刪除資料
     * @return
     */
    @Delete("delete from t_person where per_id=#{perid};")
    boolean deletePersonById(int perid);
    /**
     * 根據id查詢資料
     * @return
     */
    @Results(id = "personMap" , value = {
            @Result(column = "per_id",property = "perid"),
            @Result(column = "per_name",property = "pername"),
            @Result(column = "per_age",property = "perage"),
            @Result(column = "per_address",property = "peraddress"),
    })
    @Select("select * from  t_person where per_id=#{perid};")
    Person selectPersonById(int perid);
    /**
     * 查詢所有資料
     * @return
     */
    @Select("select * from  t_person;")
    @ResultMap("perMap")
    List<Person> selectPerson();
}

  注意:
  org.apache.ibatis.binding.BindingException: Type interface com.wangxing.mybatis.mapper.PersonMapper is not known to the MapperRegistry.
  因為沒有在MyBatis的核心配置檔案中註冊資料訪問介面,
  解決方法:在MyBatis的核心配置檔案中註冊資料訪問介面

<mappers>
    <!--註冊資料訪問介面-->
    <mapper resource="PersonMapper.xml"></mapper>
</mappers>

  MyBatis 官方推薦使用的是 sql對映檔案的方式配置sql語句,因為在工作中,SQL 的複雜度遠遠超過我們現在看到的 SQL,比如下面這條 SQL。

select * from t_user u
left join t_user_role ur on u.id = ur.user_id
left join t_role r on ur.role_id = r.id
left join t_user_info ui on u.id = ui.user_id
left join t_female_health fh on u.id = fh.user_id
left join t_male_health mh on u.id = mh.user_id
where u.user_name like concat('%', ${userName},'%')
and r.role_name like concat('%', ${roleName},'%')
and u.sex = 1
and ui.head_image is not null;

  顯然這條 SQL 比較複雜,如果放入 @Select 中會明顯增加註解的內容。如果把大量的SQL 放入 java 程式碼中,顯然程式碼的可讀性也會下降。如果同時還要考慮使用動態 SQL,比如當引數 userName 為空,則不使用 u.user_name like concat('%',${userName},'%')作為查詢條件;當 roleName 為空,則不使用 r.role_name like concat('%',${roleName},'%')作為查詢條件,但是還需要加入其他的邏輯,這樣就使得這個註解更加複雜了,不利於日後的維護和修改。
  用一張圖來展示 MyBatis 核心元件之間的關係。

在這裡插入圖片描述

相關文章