MyBatis入門第2天--MyBatis基礎知識(二)

文檔版本 開發工具 測試平臺 工程名字 日期 作者 備註
V1.0 2016.06.26 lutianfei none

mybatis開發dao的方法

SqlSession使用範圍

  • SqlSessionFactoryBuilder

    • 通過SqlSessionFactoryBuilder創建會話工廠SqlSessionFactory
    • 將SqlSessionFactoryBuilder當成一個工具類使用即可,不需要使用單例管理SqlSessionFactoryBuilder。
    • 在需要創建SqlSessionFactory時候,只需要new一次SqlSessionFactoryBuilder即可。
  • SqlSessionFactory

    • 通過SqlSessionFactory創建SqlSession,使用單例模式管理sqlSessionFactory(工廠一旦創建,使用一個實例)。
    • 將來mybatis和spring整合後,使用單例模式管理sqlSessionFactory。
  • SqlSession

    • SqlSession是一個面向用戶(程序員)的接口。
    • SqlSession中提供了很多操作數據庫的方法:如:selectOne(返回單個對象)、selectList(返回單個或多個對象)、。
    • SqlSession是線程不安全的,在SqlSesion實現類中除了有接口中的方法(操作數據庫的方法)還有數據域屬性。
    • SqlSession最佳應用場合在方法體內,定義成局部變量使用。
  • SqlSession執行過程如下:

    • 1、 加載數據源等配置信息
      • Environment environment = configuration.getEnvironment();
    • 2、 創建數據庫鏈接
    • 3、 創建事務對象
    • 4、 創建Executor,SqlSession所有操作都是通過Executor完成,mybatis源碼如下:
    if (ExecutorType.BATCH == executorType) {
          executor = newBatchExecutor(this, transaction);
        } elseif (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
    if (cacheEnabled) {
          executor = new CachingExecutor(executor, autoCommit);
        }
* 5、 SqlSession的實現類即DefaultSqlSession,此對象中對操作數據庫實質上用的是Executor
  • 結論:
    • 每個線程都應該有它自己的SqlSession實例。SqlSession的實例不能共享使用,它也是線程不安全的。因此最佳的範圍是請求或方法範圍。絕對不能將SqlSession實例的引用放在一個類的靜態字段或實例字段中。
    • 打開一個 SqlSession;使用完畢就要關閉它。通常把這個關閉操作放到 finally 塊中以確保每次都能執行關閉。如下:
SqlSession session = sqlSessionFactory.openSession();
    try {
          // do work
    } finally {
          session.close();
    }


原始dao開發方法

  • 程序員需要寫dao接口和dao實現類
  • 思路

    • 程序員需要寫dao接口和dao實現類。
    • 需要向dao實現類中注入SqlSessionFactory,在方法體內通過SqlSessionFactory創建SqlSession
  • dao接口


  • dao接口實現類
public class UserDaoImpl implements UserDao {

    // 需要向dao實現類中注入SqlSessionFactory
    // 這裏通過構造方法注入
    private SqlSessionFactory sqlSessionFactory;

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

    @Override
    public User findUserById(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = sqlSession.selectOne("test.findUserById", id);

        // 釋放資源
        sqlSession.close();

        return user;

    }

    @Override
    public List<User> findUserByName(String name) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        List<User> list = sqlSession.selectList("test.findUserByName", name);

        // 釋放資源
        sqlSession.close();

        return list;
    }


    @Override
    public void insertUser(User user) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //執行插入操作
        sqlSession.insert("test.insertUser", user);

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

        // 釋放資源
        sqlSession.close();

    }

    @Override
    public void deleteUser(int id) throws Exception {
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //執行插入操作
        sqlSession.delete("test.deleteUser", id);

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

        // 釋放資源
        sqlSession.close();

    }


  • 測試代碼:
public class UserDaoImplTest {

    private SqlSessionFactory sqlSessionFactory;

    // 此方法是在執行testFindUserById之前執行
    @Before
    public void setUp() throws Exception {
        // 創建sqlSessionFactory

        // mybatis配置文件
        String resource = "SqlMapConfig.xml";
        // 得到配置文件流
        InputStream inputStream = Resources.getResourceAsStream(resource);

        // 創建會話工廠,傳入mybatis的配置文件信息
        sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(inputStream);
    }

    @Test
    public void testFindUserById() throws Exception {
        // 創建UserDao的對象
        UserDao userDao = new UserDaoImpl(sqlSessionFactory);

        // 調用UserDao的方法
        User user = userDao.findUserById(1);

        System.out.println(user);
    }

}


原始dao開發問題
  • dao接口實現類方法中存在大量模板方法,設想能否將這些代碼提取出來,大大減輕程序員的工作量。
  • 調用sqlsession方法時將statement的id硬編碼了
  • 調用sqlsession方法時傳入的變量,由於sqlsession方法使用泛型,即使變量類型傳入錯誤,在編譯階段也不報錯,不利於程序員開發。

mapper代理方法

  • 程序員只需要mapper接口(相當 於dao接口)

mapper代理開發規範

  • 程序員還需要編寫mapper.xml映射文件
  • 程序員編寫mapper接口需要遵循一些開發規範,這樣Mybatis可以自動生成mapper接口實現類代理對象。

  • 開發規範:

    • 1、在mapper.xml中namespace等於mapper接口地址
      • 2、mapper.java接口中的方法名和mapper.xml中statement的id一致
    • 3、mapper.java接口中的方法輸入參數類型和mapper.xml中statement的parameterType指定的類型一致。
    • 4、mapper.java接口中的方法返回值類型和mapper.xml中statement的resultType指定的類型一致。

  • 總結:
    • 以上開發規範主要是對下邊的代碼進行統一生成:
User user = sqlSession.selectOne("test.findUserById", id);
sqlSession.insert("test.insertUser", user);
  • 編寫XXXmapper.java
public interface UserMapper {

    //用戶信息綜合查詢
    public List<UserCustom> findUserList(UserQueryVo userQueryVo) throws Exception;

    //用戶信息綜合查詢總數
    public int findUserCount(UserQueryVo userQueryVo) throws Exception;

    //根據id查詢用戶信息
    public User findUserById(int id) throws Exception;

    //根據id查詢用戶信息,使用resultMap輸出
    public User findUserByIdResultMap(int id) throws Exception;


    //根據用戶名列查詢用戶列表
    public List<User> findUserByName(String name)throws Exception;

    //插入用戶
    public void insertUser(User user)throws Exception;

    //刪除用戶
    public void deleteUser(int id)throws Exception;

}


  • XXXmapper.xml



  • 在SqlMapConfig.xml中加載mapper.xml
    <!-- 加載 映射文件 -->
    <mappers>
         <mapper class="cn.itcast.mybatis.mapper.UserMapper"/>
        <!-- 批量加載mapper
        指定mapper接口的包名,mybatis自動掃描包下邊所有mapper接口進行加載,遵循一些規範:需要將mapper接口類名和mapper.xml映射文件名稱保持一致,且在一個目錄中
        上邊規範的前提是:使用的是mapper代理方法
         -->
        <package name="cn.itcast.mybatis.mapper"/>

    </mappers>
  • 測試
    @Test
    public void testFindUserById() throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        //創建UserMapper對象,mybatis自動生成mapper代理對象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //調用userMapper的方法

        User user = userMapper.findUserById(1);

        System.out.println(user);


    }
  • 一些問題總結
    • 代理對象內部調用selectOne或selectList ?
      • 如果mapper方法返回單個pojo對象(非集合對象),代理對象內部通過selectOne查詢數據庫。
      • 如果mapper方法返回集合對象,代理對象內部通過selectList查詢數據庫。
    • mapper接口方法參數只能有一個是否影響系統擴展維護?
      • 系統框架中,dao層的代碼是被業務層公用的。即使mapper接口只有一個參數,可以使用包裝類型的pojo滿足不同的業務方法的需求。
      • 注意:持久層方法的參數可以包裝類型、map、list等等;
      • 但service方法中建議不要使用包裝類型(不利於業務層的可擴展)。


SqlMapConfig.xml配置文件

  • mybatis的全局配置文件SqlMapConfig.xml,配置內容如下:
    • properties(屬性)
    • settings(全局配置參數)
    • typeAliases(類型別名)
    • typeHandlers(類型處理器)
    • objectFactory(對象工廠)
    • plugins(插件)
    • environments(環境集合屬性對象)
    • environment(環境子屬性對象)
    • transactionManager(事務管理)
    • dataSource(數據源)
    • mappers(映射器)
properties屬性
  • 需求:將數據庫連接參數單獨配置在db.properties中,只需要在SqlMapConfig.xml中加載db.properties的屬性值。

  • 在SqlMapConfig.xml中就不需要對數據庫連接參數硬編碼。

  • 將數據庫連接參數只配置在db.properties中:方便對參數進行統一管理,其它xml可以引用該db.properties。
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=mysql


  • 在sqlMapConfig.xml加載屬性文件
<!-- 加載屬性文件 -->
    <properties resource="db.properties">
        <!--properties中還可以配置一些屬性名和屬性值  -->
        <!-- <property name="jdbc.driver" value=""/> -->
    </properties>

    <!-- 和spring整合後 environments配置將廢除-->
    <environments default="development">
        <environment id="development">
        <!-- 使用jdbc事務管理,事務控制由mybatis-->
            <transactionManager type="JDBC" />
        <!-- 數據庫連接池,由mybatis管理-->
            <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>
  • properties特性:
  • 注意: MyBatis 將按照下面的順序來加載屬性:
    • 在 properties 元素體內定義的屬性首先被讀取。
    • 然後會讀取properties 元素中resource或 url 加載的屬性,它會覆蓋已讀取的同名屬性。
    • 最後讀取parameterType傳遞的屬性,它會覆蓋已讀取的同名屬性。
  • 因此,通過parameterType傳遞的屬性具有最高優先級resourceurl 加載的屬性次之最低優先級的是 properties 元素體內定義的屬性。

  • 建議:

    • 不要在properties元素體內添加任何屬性值,只將屬性值定義在properties文件中。
    • 在properties文件中定義屬性名要有一定的特殊性,如:XXXXX.XXXXX.XXXX


settings全局參數配置
  • mybatis框架在運行時可以調整一些運行參數。
  • 比如:開啓二級緩存、開啓延遲加載。。
  • 全局參數將會影響mybatis的運行行爲。
  • 詳細參見“學習資料/mybatis-settings.xlsx”文件


typeAliases(別名)重點
  • 需求

    • 在mapper.xml中,定義很多的statement,statement需要parameterType指定輸入參數的類型、需要resultType指定輸出結果的映射類型。
    • 如果在指定類型時輸入類型全路徑,不方便進行開發,可以針對parameterTyperesultType 指定的類型定義一些別名,在mapper.xml中通過別名定義,方便開發。
  • 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


  • 自定義別名
  • 單個別名定義

  • 引用別名:

  • 批量定義別名(常用)

<!-- 別名定義 -->
    <typeAliases>

        <!-- 針對單個別名定義
        type:類型的路徑
        alias:別名
         -->
        <!-- <typeAlias type="cn.itcast.mybatis.po.User" alias="user"/> -->
        <!-- 批量別名定義 
        指定包名,mybatis自動掃描包中的po類,自動定義別名,別名就是類名(首字母大寫或小寫都可以)
        -->
        <package name="cn.itcast.mybatis.po"/>

    </typeAliases>


typeHandlers(類型處理器)
  • mybatis中通過typeHandlers完成jdbc類型和java類型的轉換。
  • 通常情況下,mybatis提供的類型處理器滿足日常需要,不需要自定義
  • mybatis支持類型處理器:
類型處理器 Java類型 JDBC類型
BooleanTypeHandler Boolean,boolean 任何兼容的布爾值
ByteTypeHandler Byte,byte 任何兼容的數字或字節類型
ShortTypeHandler Short,short 任何兼容的數字或短整型
IntegerTypeHandler Integer,int 任何兼容的數字和整型
LongTypeHandler Long,long 任何兼容的數字或長整型
FloatTypeHandler Float,float 任何兼容的數字或單精度浮點型
DoubleTypeHandler Double,double 任何兼容的數字或雙精度浮點型
BigDecimalTypeHandler BigDecimal 任何兼容的數字或十進制小數類型
StringTypeHandler String CHAR和VARCHAR類型
ClobTypeHandler String CLOB和LONGVARCHAR類型
NStringTypeHandler String NVARCHAR和NCHAR類型
NClobTypeHandler String NCLOB類型
ByteArrayTypeHandler byte[] 任何兼容的字節流類型
BlobTypeHandler byte[] BLOB和LONGVARBINARY類型
DateTypeHandler Date(java.util) TIMESTAMP類型
DateOnlyTypeHandler Date(java.util) DATE類型
TimeOnlyTypeHandler Date(java.util) TIME類型
SqlTimestampTypeHandler Timestamp(java.sql) TIMESTAMP類型
SqlDateTypeHandler Date(java.sql) DATE類型
SqlTimeTypeHandler Time(java.sql) TIME類型
ObjectTypeHandler 任意 其他或未指定類型
EnumTypeHandler Enumeration類型 VARCHAR-任何兼容的字符串類型,作爲代碼存儲(而不是索引)。


mappers(映射配置)
  • 通過resource加載單個映射文件,<mapper resource=" " />使用相對於類路徑的資源
    • 如:<mapper resource="sqlmap/User.xml" />


  • 使用完全限定路徑<mapper url=" " />

    • 如:<mapper url="file:///D:\workspace_spingmvc\mybatis_01\config\sqlmap\User.xml" />
  • 通過mapper接口加載單個mapper

    • 規範:mapper**接口類名和mapper.xml映射文件名稱必須保持一致,且在一個package目錄中**;並且要使用mapper代理方法
<!-- 通過mapper接口加載單個映射文件需要遵循一些規範:mapper接口類名和mapper.xml映射文件名稱必須保持一致,且在一個package目錄中;並且要使用mapper代理方法-->
    <mapper class="cn.itcast.mybatis.mapper.UserMapper"/>


  • 批量加載mapper(推薦使用)
    • 當指定mapper接口的包名時,mybatis會自動掃描包下邊所有mapper接口進行加載
    • 應遵循的一些規範:需要將mapper**接口類名和mapper.xml映射文件名稱保持一致,且在一個package目錄中;並且使用的是**mapper代理方法
<package name="cn.itcast.mybatis.mapper"/>


輸入映射

  • 通過parameterType指定輸入參數的類型,類型可以是簡單類型、hashmap、pojo的包裝類型。
傳遞pojo的包裝對象
  • 需求:

    • 完成用戶信息的綜合查詢,需要傳入查詢條件很複雜(可能包括用戶信息、其它信息,比如商品、訂單的)
  • 定義包裝類型pojo

    • 針對上邊需求,建議使用自定義的包裝類型的pojo。在包裝類型的pojo中將複雜的查詢條件包裝進去。


  • 映射文件XXXmapper.xml設置
    • 在UserMapper.xml中定義用戶信息綜合查詢(查詢條件複雜,通過高級查詢進行復雜關聯查詢)。

  • mapper.java

  • 測試代碼


傳遞hashmap
  • Sql映射文件定義如下:
    • #中 id 和 username是hashmap的key。
<!-- 傳遞<u>hashmap</u>綜合查詢用戶信息 -->
    <select id="findUserByHashmap" parameterType="hashmap" resultType="user">

       select * from user where id=#{id} and username like '%${username}%'

    </select>


  • 測試:
Public void testFindUserByHashmap()throws Exception{
        //獲取session
        SqlSession session = sqlSessionFactory.openSession();
        //獲限mapper接口實例
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //構造查詢條件Hashmap對象
        HashMap<String, Object> map = new HashMap<String, Object>();
        map.put("id", 1);
        map.put("username", "管理員");

        //傳遞Hashmap對象查詢用戶列表
        List<User>list = userMapper.findUserByHashmap(map);
        //關閉session
        session.close();
    }
  • 異常測試:
    • 傳遞的map中的key和sql中解析的key不一致。
    • 測試結果沒有報錯,只是通過key獲取值爲空。


輸出映射

resultType
  • 使用resultType進行輸出映射,只有查詢出來的列名pojo中的屬性名一致,該列纔可以映射成功。

    • 如果查詢出來的列名和pojo中的屬性名全部不一致,沒有創建pojo對象。
    • 只要查詢出來的列名和pojo中的屬性有一個一致,就會創建pojo對象。
  • 輸出簡單類型

  • 需求

    • 用戶信息的綜合查詢列表總數,通過查詢總數和上邊用戶綜合查詢列表纔可以實現分頁。
  • Usermapper.xml


  • Usermapper.java

  • 測試代碼

  • 小結

    • 查詢出來的結果集只有一行且一列,可以使用簡單類型進行輸出映射。
  • 輸出pojo對象和pojo列表

  • 不管是輸出的pojo單個對象還是一個列表(list中包括pojo),在mapper.xml中resultType指定的類型是一樣的。
  • 在mapper.java指定的方法返回值類型不一樣:

    • 1、輸出單個pojo對象,方法返回值是單個對象類型
    • 2、輸出pojo對象list,方法返回值是List<Pojo>
  • 生成的動態代理對象中是根據mapper方法的返回值類型確定是調用selectOne(返回單個對象調用)還是selectList (返回集合對象調用)


resultMap
  • mybatis中使用resultMap完成高級輸出結果映射。

  • resultMap使用方法

    • 如果查詢出來的列名pojo的屬性名不一致,通過定義一個resultMap列名和pojo屬性名之間作一個映射關係
  • 案例: 將下邊的sql使用User完成映射

    • User類中屬性名和上邊查詢列名不一致。
SELECT id id_,username username_ FROM USER WHERE id=#{value}


  • 1、在UserMapper.xml中定義resultMap

<!-- 定義resultMap
    將SELECT id id_,username username_ FROM USER 和User類中的屬性作一個映射關係

    type:resultMap最終映射的java對象類型,可以使用別名
    id:對resultMap的唯一標識
-->
     <resultMap type="user" id="userResultMap">
         <!-- id表示查詢結果集中唯一標識 
         column:查詢出來的列名
         property:type指定的pojo類型中的屬性名
         最終resultMap對column和property作一個映射關係 (對應關係)
         -->
         <id column="id_" property="id"/>
         <!-- 
         result:對普通名映射定義
         column:查詢出來的列名
         property:type指定的pojo類型中的屬性名
         最終resultMap對column和property作一個映射關係 (對應關係)
          -->
         <result column="username_" property="username"/>

     </resultMap>



  • 2、使用resultMap作爲statement的輸出映射類型
    <!-- 使用resultMap進行輸出映射
    resultMap:指定定義的resultMap的id,如果這個resultMap在其它的mapper文件,前邊需要加namespace
    -->
    <select id="findUserByIdResultMap" parameterType="int" resultMap="userResultMap">
        SELECT id id_,username username_ FROM USER WHERE id=#{value}
    </select>


  • mapper.java

  • 測試

    @Test
    public void testFindUserByIdResultMap() throws Exception {

        SqlSession sqlSession = sqlSessionFactory.openSession();

        //創建UserMapper對象,mybatis自動生成mapper代理對象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        //調用userMapper的方法

        User user = userMapper.findUserByIdResultMap(1);

        System.out.println(user);


    }


  • 小結
    • 使用resultType進行輸出映射,只有查詢出來的列名和pojo中的屬性名一致,該列纔可以映射成功。
    • 如果查詢出來的列名和pojo的屬性名不一致,通過定義一個resultMap對列名和pojo屬性名之間作一個映射關係。

動態sql

  • 什麼是動態sql

    • mybatis核心 對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝。
  • 需求

    • 用戶信息綜合查詢列表和用戶信息查詢列表總數這兩個statement的定義使用動態sql。
    • 對查詢條件進行判斷,如果輸入參數不爲空才進行查詢條件拼接。
  • mapper.xml


  • 測試代碼


sql片段

  • 需求

    • 將上邊實現的動態sql判斷代碼塊抽取出來,組成一個sql片段。其它的statement中就可以引用sql片段。方便程序員進行開發。
  • 定義sql片段,在UserMapper.xml頂部

  • 引用sql片段
    • 在mapper.xml中定義的statement中引用sql片段:


foreach

  • 向sql傳遞數組或List,mybatis使用foreach解析

  • 需求

    • 在用戶查詢列表和查詢總數的statement中增加多個id輸入查詢。sql語句如下:
//兩種方法:
SELECT * FROM USER WHERE id=1 OR id=10 OR id=16
SELECT * FROM USER WHERE id IN(1,10,16)
  • 在輸入參數類型中添加List<Integer> ids傳入多個id


  • 修改mapper.xml
    • 對於此類型Sql語句:WHERE id=1 OR id=10 OR id=16
    • 在查詢條件中,查詢條件定義成一個sql片段,需要修改sql片段。
    <sql id="query_user_where">
        <if test="userCustom!=null">
            <if test="userCustom.sex!=null and userCustom.sex!=''">
                and user.sex = #{userCustom.sex}
            </if>
            <if test="userCustom.username!=null and userCustom.username!=''">
                and user.username LIKE '%${userCustom.username}%'
            </if>
            <if test="ids!=null">
            <!-- 使用 foreach遍歷傳入ids
            collection:指定輸入 對象中集合屬性
            item:每個遍歷生成對象中
            open:開始遍歷時拼接的串
            close:結束遍歷時拼接的串
            separator:遍歷的兩個對象中需要拼接的串
             -->
             <!-- 使用實現下邊的sql拼接:
              AND (id=1 OR id=10 OR id=16) 
              -->
            <foreach collection="ids" item="user_id" open="AND (" close=")" separator="or">
                <!-- 每個遍歷需要拼接的串 -->
                id=#{user_id}
            </foreach>

            <!-- 實現  “ and id IN(1,10,16)”拼接 -->
            <!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
                每個遍歷需要拼接的串
                #{user_id}
            </foreach> -->

            </if>
        </if>
    </sql>

  • 測試代碼

  • 在UserQueryVo中:

public class UserQueryVo {

    //傳入多個id
    private List<Integer> ids;

    public List<Integer> getIds() {
        return ids;
    }

    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
}


  • 對於另外一個sql的實現:
<!-- 實現  “ and id IN(1,10,16)”拼接 -->
<!-- <foreach collection="ids" item="user_id" open="and id IN(" close=")" separator=",">
    每個遍歷需要拼接的串
    #{user_id}
</foreach> -->
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章