MyBatis學(xué)習(xí)筆記

最近學(xué)習(xí)MyBatis這個輕量型持久層框架,感覺入門很簡單,但是深層次細(xì)節(jié)配置很多。本篇筆記從 配置文件->例子入門->MyBatis傳參和取參->查詢結(jié)果返回類型->關(guān)聯(lián)數(shù)據(jù)查詢->關(guān)聯(lián)數(shù)據(jù)查詢策略(是否啟用懶加載)->動態(tài)SQL->一級緩存和二級緩存來進(jìn)行一次MyBatis探尋。

一、概念簡介

MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設(shè)置參數(shù)以及獲取結(jié)果集。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄。

下載地址:https://github.com/mybatis/mybatis-3
MyBatis 文檔:http://www.mybatis.org/mybatis-3/zh/index.html
mybatis-spring 文檔:http://www.mybatis.org/spring/zh/index.html

開始總結(jié)前先導(dǎo)入Maven依賴

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

二、總配置文件

在classpath目錄下面建立一個MyBatis配置文件,配置文件可以分為兩個部分,第一部分?jǐn)?shù)據(jù)庫環(huán)境配置,第二部分加載映射文件。
文件名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 文件 -->
    <properties resource="jdbc.properties"></properties>
    
    <environments default="debug">
        <environment id="debug">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://10.22.70.2:3306/database_chao?useUnicode=true&amp;characterEncoding=utf8" />
                <property name="username" value="xxx" />
                <property name="password" value="xxx" />
            </dataSource>
        </environment>

        <environment id="release">
            <transactionManager type="JDBC"></transactionManager>
            <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>
        
    </mappers>
</configuration>

MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設(shè)置(settings)和屬性(properties)信息。文檔的頂層結(jié)構(gòu)如下:
---configuration 配置
-------properties 屬性
-------settings 設(shè)置
-------typeAliases 類型別名
-------typeHandlers 類型處理器
-------plugins 插件
-------environments 環(huán)境
----------environment 環(huán)境變量
-------------transactionManager 事務(wù)管理器
-------------dataSource 數(shù)據(jù)源
-------------databaseIdProvider 數(shù)據(jù)庫廠商標(biāo)識
-------mappers 映射器

有關(guān) properties、settings、typeAliases、typeHandlers和plugins的配置及相關(guān)功能請參考MyBatis的官方文檔 http://www.mybatis.org/mybatis-3/zh/configuration.html


三、例子入門

1. 簡單例子入門

① 建立POJO類

public class Grade {
    private int gid;
    private String gname;
}

② 創(chuàng)建POJO對象和Mysql數(shù)據(jù)的表之間的映射配置
[在POJO同在包下]創(chuàng)建名字為 GradeMapper.xml 的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="primary">
    <select id="selectGrade" resultType="primary.Grade">
        select * from grade where gid=#{id}
    </select>
</mapper>

namespace 可以自己隨便定,不要重復(fù)
<select> 標(biāo)簽里面的 id 也可以自己隨便定,不要重復(fù)

③ 將映射配置加載到總配置文件中
mybatis-config.xml文件中的<mappers> 標(biāo)簽下加入如下代碼

<mappers>
    <mapper resource="primary/GradeMapper.xml" />
</mappers>

④ 代碼測試

@Test
public void testSelectById() throws Exception {
    //加載 配置文件
    InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    //初始化session工廠
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //1. 執(zhí)行SQL語句標(biāo)簽里面的 id為 映射配置里面的 namespace + 標(biāo)簽的id
    //2. 參數(shù)為映射文件里面執(zhí)行sql語句需要的參數(shù), 這里是id
    Object selectOne = sqlSession.selectOne("primary.selectGrade", 1);
    //3. MyBatis將查詢數(shù)封裝成要求的結(jié)果返回類型后再返回
    System.out.println(selectOne);
    
    sqlSession.close();
    inputStream.close();
}
2. 正規(guī)寫法 實現(xiàn)簡單增刪改查功能

開發(fā)跟數(shù)據(jù)交互都是通過 Dao層,MyBatis為我們Dao層開發(fā)做了很好的兼容,我們直接寫接口,然后用Mapper映射文件充當(dāng)實現(xiàn)類。

① 先寫接口

public interface GradeDao {
    public int insertGrade(Grade grade);
    public int updateGrade(Grade grade);
    public int deleteGrade(int id);
    public List<Grade> selectAll();
}

② 創(chuàng)建POJO對象和Mysql數(shù)據(jù)的表之間的映射配置
為了方便查看和加載配置文件,我們一般Mapper文件和對應(yīng)的Dao接口放在同一個包下。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="com.ogemray.dao.GradeDao">

    <insert id="insertGrade">
        insert into grade (gname) value (#{gname})
    </insert>
    
    <update id="updateGrade">
        update grade set gname=#{gname} where gid=#{gid}
    </update>
    
    <delete id="deleteGrade">
        delete from grade where gid=#{id}
    </delete>
    
    <select id="selectAll" resultType="com.ogemray.entity.Grade">
        select * from grade
    </select>
</mapper>

配置文件有些要注意的地方
mapper 的 namespace 要是 對應(yīng) Dao接口的全名
SQL語句標(biāo)簽里面的 id 要是 Dao里面對應(yīng)方法的名字

③ 測試代碼

public class UpgradeTest {
    private InputStream inputStream;
    private SqlSession sqlSession;
    
    @Before
    public void beforeAction() throws Exception {
        inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        sqlSession = sessionFactory.openSession();
    }
    
    @After
    public void afterAction() throws Exception {
        sqlSession.commit();
        sqlSession.close();
        inputStream.close();
    }
    
    @Test
    public void testSimpleInsert() {
        Grade grade = new Grade("理科四班");
        GradeDao mapper = sqlSession.getMapper(GradeDao.class);
        int count = mapper.insertGrade(grade);
        System.out.println(count);
    }
    
    @Test
    public void testSimpleUpdate() {
        GradeDao mapper = sqlSession.getMapper(GradeDao.class);
        Grade grade = mapper.selectOneById(5);
        int count = grade.setGname("理科二班");
        mapper.updateGrade(grade);
    }
    
    @Test
    public void testSimpleDelete() {
        GradeDao mapper = sqlSession.getMapper(GradeDao.class);
        int count = mapper.deleteGrade(5);
    }

    @Test
    public void testSimpleSelectAll() {
        GradeDao mapper = sqlSession.getMapper(GradeDao.class);
        List<Grade> list = mapper.selectAll();
        for (Grade grade : list) { System.out.println(grade); }
    }
}
3. 得到新插入對象的id值

對于上面的 增 刪 更新后面返回的都是影響行數(shù),很多時候數(shù)據(jù)庫主鍵自增長,但是我們要知道新插入對象對應(yīng)數(shù)據(jù)庫里面的id值,這個時候需要用到下面這兩個屬性。

屬性 描述
useGeneratedKeys (僅對 insert 和 update 有用)這會令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法來取出由數(shù)據(jù)庫內(nèi)部生成的主鍵(比如:像 MySQL 和 SQL Server 這樣的關(guān)系數(shù)據(jù)庫管理系統(tǒng)的自動遞增字段),默認(rèn)值:false。
keyProperty (僅對 insert 和 update 有用)唯一標(biāo)記一個屬性,MyBatis 會通過 getGeneratedKeys 的返回值或者通過 insert 語句的 selectKey 子元素設(shè)置它的鍵值,默認(rèn):unset。如果希望得到多個生成的列,也可以是逗號分隔的屬性名稱列表。

Mapper文件里面的標(biāo)簽做如下更改

<insert id="insertGrade" 
        parameterType="com.ogemray.entity.Grade"
        useGeneratedKeys="true" 
        keyProperty="gid">
    insert into grade (gname) value (#{gname})
</insert>

測試代碼

@Test
public void testSimpleInsert() {
    Grade grade = new Grade("理科五班");
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    int count = mapper.insertGrade(grade);
    System.out.println("影響行數(shù): " + count + "  更新后對象: " + grade);
}

控制臺輸出

影響行數(shù): 1  更新后對象: Grade [gid=8, gname=理科五班]

四、參數(shù)傳遞 (多參傳遞)

1. 使用 arg 取參 arg0、arg1 ...

Dao接口

public interface GradeDao {
    public List<Grade> selectPartGradeByUseArg(int startId, int endId);
}

Mapper文件

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeByUseArg" resultType="com.ogemray.entity.Grade">
        select * from grade where gid &gt;= #{arg0} and gid &lt;= #{arg1}
    </select>
</mapper>

測試代碼

@Test
public void testMultiParam1() {
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    List<Grade> list = mapper.selectPartGradeByUseArg(1, 2);
    for (Grade grade : list) { System.out.println(grade); }
}
2. 使用 param 取參 param1、param2 ...

Dao接口

public interface GradeDao {
    public List<Grade> selectPartGradeByUseParam(int startId, int endId);
}

Mapper文件

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeByUseParam" resultType="com.ogemray.entity.Grade">
        select * from grade where gid &gt;= #{param1} and gid &lt;= #{param2}
    </select>
</mapper>

測試代碼

@Test
public void testMultiParam2() {
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    List<Grade> list = mapper.selectPartGradeByUseParam(1, 2);
    for (Grade grade : list) { System.out.println(grade); }
}
3. 使用 @Param 注解傳參

這種方式比較常用,標(biāo)明參數(shù)名字,代碼閱讀性強(qiáng)

Dao接口

public interface GradeDao {
    public List<Grade> selectPartGradeUseAnno(@Param("startId") int startId,@Param("endId") int endId);
}

Mapper文件

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeUseAnno" resultType="com.ogemray.entity.Grade">
        select * from grade where gid &gt;= #{startId} and gid &lt;= #{endId}
    </select>
</mapper>

測試代碼

@Test
public void testMultiParam3() {
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    List<Grade> list = mapper.selectPartGradeUseAnno(1, 2);
    for (Grade grade : list) { System.out.println(grade); }
}
4. 使用 Map 傳參

Dao接口

public interface GradeDao {
    public List<Grade> selectPartGradeUseMap(Map<String, Integer> map);
}

Mapper文件

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeUseMap" resultType="com.ogemray.entity.Grade">
        select * from grade where gid &gt;= #{startId} and gid &lt;= #{endId}
    </select>
</mapper>

測試代碼

@Test
public void testMultiParam4() {
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    HashMap<String,Integer> map = new HashMap<String, Integer>();
    map.put("startId", 1);
    map.put("endId", 2);
    List<Grade> list = mapper.selectPartGradeUseMap(map);
    for (Grade grade : list) { System.out.println(grade); }
}
5. 使用集合傳參

Dao接口

public interface GradeDao {
    public List<Grade> selectPartGradeUseCollection(List<Integer> gids);
}

Mapper文件

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeUseCollection" resultType="com.ogemray.entity.Grade">
        <!-- select * from grade where gid &gt;= #{collection[0]} and gid &lt;= #{collection[1]} -->
        select * from grade where gid &gt;= #{list[0]} and gid &lt;= #{list[1]}
    </select>
</mapper>

測試代碼

@Test
public void testMultiParam5() {
    GradeDao mapper = sqlSession.getMapper(GradeDao.class);
    List<Integer> gids = new ArrayList<Integer>();
    gids.add(1);
    gids.add(2);
    List<Grade> list = mapper.selectPartGradeUseCollection(gids);
    for (Grade grade : list) { System.out.println(grade); }
}

最后說下映射文件中獲取參數(shù)的符號#{}和${}的區(qū)別

#{}對應(yīng)的是 PreparedStatementd 對象來執(zhí)行 sql 語句
${}對應(yīng)的是 Statement 對象來執(zhí)行 sql 語句

public interface GradeDao {
    public List<Grade> selectPartGradeUse(@Param("tableName") String tableName,@Param("startId") int startId, @Param("endId") int endId);
}
<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectPartGradeUse" resultType="com.ogemray.entity.Grade">
        select * from ${tableName} where gid &gt;= ${startId} and gid &lt;= #{endId}
    </select>
</mapper>

最后執(zhí)行的 SQL語句可以看出區(qū)別,也可以看出兩者的使用場景

select * from grade where gid >= 1 and gid <= ? 

五、結(jié)果類型 resultType 和 resultMap

屬性 描述
resultType 從這條語句中返回的期望類型的類的完全限定名或別名。注意如果是集合情形,那應(yīng)該是集合可以包含的類型,而不能是集合本身。使用 resultType 或 resultMap,但不能同時使用。
resultMap 外部 resultMap 的命名引用。結(jié)果集的映射是 MyBatis 最強(qiáng)大的特性,對其有一個很好的理解的話,許多復(fù)雜映射的情形都能迎刃而解。使用 resultMap 或 resultType,但不能同時使用。
1. resultType 指定返回封裝好的Java對象
<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectOneById_returnGrade" resultType="com.ogemray.entity.Grade">
        select * from grade where gid = #{id}
    </select>
</mapper>

//測試控制臺輸出
Grade [gid=1, gname=文科一班]
2. resultType 指定返回封裝Java對象的集合
<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectAll_retuenList" resultType="com.ogemray.entity.Grade">
        select * from grade
    </select>
</mapper>

//測試控制臺輸出
[Grade [gid=1, gname=文科一班], Grade [gid=2, gname=文科二班]]
3. resultType 指定返回 Map 對象,key查詢出來的 column name value對應(yīng)值
<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectOneById_returnMap" resultType="map">
        select * from grade where gid = #{id}
    </select>
</mapper>

//測試控制臺輸出
{gid=1, gname=文科一班}
4. resultType 指定返回 Map 對象,key值隨便指定,value為封裝好的java對象
//接口類, 需要用 @MapKey注解指定用哪個column字段作為key
public interface GradeDao {
    @MapKey("gid")
    public Map<Integer, Grade> selectAll_retuenMap();
}

//映射文件配置
<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectAll_retuenMap" resultType="map">
        select * from grade
    </select>
</mapper>

//測試控制臺輸出
{1={gid=1, gname=文科一班}, 2={gid=2, gname=文科二班}}
5. resultMap 指定返回類型

當(dāng)在類里面屬性名和數(shù)據(jù)庫里面的column名字對不上的時候,可以用resultMap來指定字段匹配。

resultMap 子元素

  • id – 一個 ID 結(jié)果,標(biāo)記出作為 ID 的結(jié)果可以幫助提高整體性能
  • result – 注入到字段或 JavaBean 屬性的普通結(jié)果
  • association – 一個復(fù)雜類型的關(guān)聯(lián),許多結(jié)果將包裝成這種類型
    • 嵌套結(jié)果映射 – 關(guān)聯(lián)可以指定為一個 resultMap 元素,或者引用一個
  • collection – 一個復(fù)雜類型的集合
    • 嵌套結(jié)果映射 – 集合可以指定為一個 resultMap 元素,或者引用一個

resultMap 屬性

屬性 描述
id 當(dāng)前命名空間中的一個唯一標(biāo)識,用于標(biāo)識一個result map
type 類的完全限定名, 或者一個類型別名
autoMapping 如果設(shè)置這個屬性,MyBatis將會為這個ResultMap開啟或者關(guān)閉自動映射。這個屬性會覆蓋全局的屬性 autoMappingBehavior。默認(rèn)值為:unset。

例如下面實體類和實體類在數(shù)據(jù)庫映射字段對比

Java類和數(shù)據(jù)庫表字段對比.png
//映射配置文件中
<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="selectAll" resultMap="studentMap">
        select * from student
    </select>

    <resultMap type="com.ogemray.entity.Student" id="studentMap">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="birthday" column="birthday"/>
    </resultMap>
</mapper>

如上面代碼,通過 <resultMap> 標(biāo)簽來指定實體類里面的屬性和映射數(shù)據(jù)表里面字段一一對應(yīng)關(guān)系,兩者一樣的可以省略指定。然后通過 <select> 標(biāo)簽里面的 resultMap 屬性來引用提前設(shè)置好的 <resultMap> 標(biāo)簽。


六、resultMap 實現(xiàn)關(guān)聯(lián)查詢

MyBatis框架也可以實現(xiàn)像Hibernate那樣的關(guān)聯(lián)查詢,只不過是需要自己手動去配置。

在開始之前,先介紹下涉及到關(guān)聯(lián)查詢的兩個標(biāo)簽 <association><collection> 里面的屬性字段

屬性 描述
property 映射到列結(jié)果的字段或?qū)傩?/td>
javaType 一個 Java 類的完全限定名,或一個類型別名
column 數(shù)據(jù)庫的列名,注意: 要處理復(fù)合主鍵,你可以指定多個列名通過 column="{prop1=col1,prop2=col2}" 這種語法來傳遞給嵌套查詢語句。這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。
select 另外一個映射語句的 ID,可以加載這個屬性映射需要的復(fù)雜類型。獲取的在列屬性中指定的列的值將被傳遞給目標(biāo) select 語句作為參數(shù)。 select 注 意 : 要處理復(fù)合主鍵,你可以指定多個列名通過 column= " {prop1=col1,prop2=col2} " 這種語法來傳遞給嵌套查詢語句,這會引起 prop1 和 prop2 以參數(shù)對象形式來設(shè)置給目標(biāo)嵌套查詢語句。

下面通過Student類和Grade類來建立關(guān)系進(jìn)行關(guān)聯(lián)查詢示例

Student類和實體表映射.png
Grade類和實體表映射.png
1. 通過連級方式實現(xiàn)關(guān)聯(lián)查詢
<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="primaryCorrelationQueryById" resultMap="studentMap2">
        select
            s.sid sid, 
            s.sname sname,
            s.birthday birthday,
            g.gid gid,
            g.gname gname
        from 
            student s, grade g 
        where 
            s.sid = #{id} and s.sid = g.gid
    </select>
    
    <resultMap type="com.ogemray.entity.Student" id="studentMap2">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="birthday" column="birthday"/>
        <!-- 通過級聯(lián)方法, 實現(xiàn)關(guān)聯(lián)查詢 -->
        <result property="grade.gid" column="gid"/>
        <result property="grade.gname" column="gname"/>
    </resultMap>
</mapper>
2. 使用 <association> 標(biāo)簽實現(xiàn)關(guān)聯(lián)查詢
<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="correlationQueryById" resultMap="studentMap3">
        select
            s.sid sid, 
            s.sname sname,
            s.birthday birthday,
            g.gid gid,
            g.gname gname
        from 
            student s, grade g 
        where 
            s.sid = #{id} and s.sid = g.gid
    </select>
    
    <resultMap type="com.ogemray.entity.Student" id="studentMap3">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="birthday" column="birthday"/>
        <!-- 通過子標(biāo)簽 association, 實現(xiàn)關(guān)聯(lián)查詢 -->
        <association property="grade" javaType="com.ogemray.entity.Grade">
            <id property="gid" column="grade_id"/>
            <result property="gname" column="gname"/>
        </association>
    </resultMap>
</mapper>
3. 使用 <association> 標(biāo)簽實現(xiàn)分步關(guān)聯(lián)查詢

GradeMapper.xml

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="selectOneById" resultType="com.ogemray.entity.Grade">
        select * from grade where gid = #{id}
    </select>
</mapper>

StudentMapper.xml

<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="substepCorrelationQueryById" resultMap="studentMap4">
        select * from student where sid = #{id}
    </select>
    
    <resultMap type="com.ogemray.entity.Student" id="studentMap4">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="birthday" column="birthday"/>
        <association property="grade" 
                     select="com.ogemray.dao.GradeDao.selectOneById"
                     column="grade_Id">
        </association>
    </resultMap>
</mapper>
//分別執(zhí)行SQL語句
DEBUG ==>  Preparing: select * from student where sid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG ====>  Preparing: select * from grade where gid = ? 
DEBUG ====> Parameters: 1(Integer)
4. 使用 <collection> 標(biāo)簽實現(xiàn) 集合關(guān)聯(lián)查詢
<collection> 里屬性 描述
property 集合對應(yīng)屬性名
ofType 集合包含元素類型

GradeMapper.xml

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="correlationQueryById" resultMap="gradeMap1">
        select 
            g.gid gid,
            g.gname gname,
            s.sid sid,
            s.sname sname,
            s.birthday birthday
        from
            grade g, student s
        where 
            g.gid = #{id} and g.gid = s.grade_id
    </select>
    
    <resultMap type="com.ogemray.entity.Grade" id="gradeMap1">
        <id property="gid" column="gid"/>
        <result property="gname" column="gname"/>
        
        <collection property="students" ofType="com.ogemray.entity.Student">
            <id property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="birthday" column="birthday"/>
        </collection>
    </resultMap>
</mapper>
5. 使用 <collection> 標(biāo)簽實現(xiàn) 分步 集合關(guān)聯(lián)查詢

StudentMapper.xml

<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="selectStudentsByGradeId" resultMap="resultMap1">
        select * from student where grade_id = #{gid}
    </select>

    <resultMap type="com.ogemray.entity.Student" id="resultMap1">
        <id property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="birthday" column="birthday"/>
    </resultMap>
</mapper>

GradeMapper.xml

<mapper namespace="com.ogemray.dao.GradeDao">
    <select id="substepCorrelationQueryById" resultMap="gradeMap2">
        select * from grade where gid = #{id}
    </select>
    
    <resultMap type="com.ogemray.entity.Grade" id="gradeMap2">
        <id property="gid" column="gid"/>
        <result property="gname" column="gname"/>
        
        <collection property="students" 
                    select="com.ogemray.dao.StudentDao.selectStudentsByGradeId"
                    column="gid">
            <id property="id" column="sid" />
            <result property="name" column="sname" />
            <result property="birthday" column="birthday" />
        </collection>
    </resultMap>
</mapper>
//分別執(zhí)行SQL語句
DEBUG ==>  Preparing: select * from grade where gid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG ====>  Preparing: select * from student where grade_id = ? 
DEBUG ====> Parameters: 1(Integer)

七、在分步查詢的基礎(chǔ)上配置懶加載

懶加載的概念,了解過Hibernate的肯定都不陌生,Hibernate中涉及到關(guān)聯(lián)查詢的時候,懶加載是默認(rèn)就開啟著的,懶加載就是在關(guān)聯(lián)查詢中,真正需要用到關(guān)聯(lián)對象的時候,才發(fā)起sql語句從數(shù)據(jù)庫中查詢數(shù)據(jù),從而實現(xiàn)提升數(shù)據(jù)庫性能的目的。
Mybatis作為一個優(yōu)秀的ORM框架當(dāng)然也支持懶加載,和Hibernate不同是,它默認(rèn)情況下是禁止了懶加載的,要使用懶加載需要手動的開啟,開啟的方法就是配置兩個全局變量:lazyLoadingEnabled設(shè)置為true,aggressiveLazyLoading設(shè)置為false

設(shè)置參數(shù) 描述 有效值 默認(rèn)值
lazyLoadingEnabled 延遲加載的全局開關(guān)。當(dāng)開啟時,所有關(guān)聯(lián)對象都會延遲加載。 特定關(guān)聯(lián)關(guān)系中可通過設(shè)置fetchType屬性來覆蓋該項的開關(guān)狀態(tài) true/false false
aggressiveLazyLoading 當(dāng)開啟時,任何方法的調(diào)用都會加載該對象的所有屬性。否則,每個屬性會按需加載 true/false false (true in ≤3.4.1)
<?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>
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>
    ...
</configuration>

拿上面第六部分的 3 和 5分別用 <association> 標(biāo)簽和 <collection> 標(biāo)簽實現(xiàn)的分步查詢來做實驗

1. 在 <association> 標(biāo)簽中實現(xiàn)懶加載

StudentMapper.xml

<mapper namespace="com.ogemray.dao.StudentDao">
    ...
    <resultMap type="com.ogemray.entity.Student" id="studentMap4">
        ...
        <association property="grade" 
                     select="com.ogemray.dao.GradeDao.selectOneById"
                     column="grade_Id">
        </association>
    </resultMap>
</mapper>

執(zhí)行測試代碼

StudentDao mapper = sqlSession.getMapper(StudentDao.class);
Student student = mapper.substepCorrelationQueryById(1);
System.out.println(student.getName());
System.out.println(student.getGrade().toString());

控制臺輸出

DEBUG ==>  Preparing: select * from student where sid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG <==      Total: 1
Mike
DEBUG ==>  Preparing: select * from grade where gid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG <==      Total: 1
Grade [gid=1, gname=文科一班]
2. 在 <collection> 標(biāo)簽中實現(xiàn)懶加載

GradeMapper.xml

<mapper namespace="com.ogemray.dao.GradeDao">
    ...
    <resultMap type="com.ogemray.entity.Grade" id="gradeMap2">
        ...
        <collection property="students" 
                    select="com.ogemray.dao.StudentDao.selectStudentsByGradeId"
                    column="gid">
            ...
        </collection>
    </resultMap>
</mapper>

執(zhí)行測試代碼

GradeDao mapper = sqlSession.getMapper(GradeDao.class);
Grade grade = mapper.substepCorrelationQueryById(1);
System.out.println(grade.getGname());
System.out.println(grade.getStudents().size());

控制臺輸出

DEBUG ==>  Preparing: select * from grade where gid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG <==      Total: 1
文科一班
DEBUG ==>  Preparing: select * from student where grade_id = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG <==      Total: 2
2
3. 設(shè)置 fetchType 屬性來配置單個關(guān)聯(lián)查詢的加載策略

<association> 標(biāo)簽和 <collection> 標(biāo)簽都都有 fetchType 這么個屬性,他是用來指定該關(guān)聯(lián)在查詢時是否啟用懶加載,本身是可選值,但是設(shè)定了之后將會取代全局的 lazyLoadingEnabled 設(shè)置。
有效值為 lazyeager


八、MyBatis 動態(tài) SQL

MyBatis 的強(qiáng)大特性之一便是它的動態(tài) SQL。如果你有使用 JDBC 或其它類似框架的經(jīng)驗,你就能體會到根據(jù)不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態(tài) SQL 這一特性可以徹底擺脫這種痛苦。

動態(tài) SQL 元素和 JSTL 或基于類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間了解。MyBatis 3 大大精簡了元素種類,現(xiàn)在只需學(xué)習(xí)原來一半的元素便可。MyBatis 采用功能強(qiáng)大的基于 OGNL 的表達(dá)式來淘汰其它大部分元素。

1. if
public interface StudentDao {
    public List<Student> selectStudentsByName(@Param("name") String name);
}
<mapper namespace="com.ogemray.dao.StudentDao">
    <select id="selectStudentsByName" resultMap="resultMap1">
        select * from student where 1=1
        <if test="name != null">
            and sname like #{name}
        </if>
    </select> 
</mapper>

需要注意以下兩點
① 在接口中加@Param注解,以防止以下異常 ReflectionException: There is no getter for property named...
② if標(biāo)簽里的test屬性里是用的OGNL表達(dá)式,這是apache下的一個標(biāo)簽,用法類似jstl,但有些小差別,具體的內(nèi)容可以在ognl官網(wǎng)上查詢,這里強(qiáng)調(diào)一點,有些符號在xml文件里寫的時候,屬于特殊符號,不能直接使用,我們可以在w3cschool里查http://www.w3school.com.cn/tags/html_ref_entities.html

2. choose, when, otherwise
<select id="selectStudentsByName" resultMap="resultMap1">
    select * from student where 1=1
    <choose>
        <when test="name != null">
            and sname like #{name}
        </when>
        <otherwise>
            and sid = 1
        </otherwise>
    </choose>
</select> 

相當(dāng)于平時用到的 if (condition) { } else { } 判斷語句

3. where
<select id="selectStudentsByName" resultMap="resultMap1">
    select * from student
    <where>
        <if test="name != null">
            sname like #{name}
        </if>
    </where>
</select>

容易實現(xiàn)動態(tài)添加 where 語句
但是遇到 where 標(biāo)簽里面有多個條件語句該怎么辦呢? 下面來看看 tirm 標(biāo)簽

4. trim
<select id="selectStudentsByName" resultMap="resultMap1">
    select * from student
    <where>
        <trim prefixOverrides="and">
            <if test="name != null">
                and sname like #{name}
            </if>
            <if test="name != null">
                and sid = 1
            </if>
        </trim>
    </where>
</select>

最終生成的 SQL語句
select * from student WHERE sname like ? and sid = 1 

trim 標(biāo)簽可以動態(tài)對一段語句的首尾進(jìn)行操作,下面來看下 trim標(biāo)簽里面的其他屬性:

prefix:加前綴
prefixOverrides:匹配的前綴去掉
suffix:加后綴
suffixOverrides:匹配的后綴去掉

5. set
public interface StudentDao {
    public int updateStudent(@Param("student") Student student);
}
<update id="updateStudent">
    update student
    <set>
        <if test="student.name != null">sname = #{student.name}</if>
    </set>
    <where>
        <if test="student.id != 0">sid = #{student.id}</if>
    </where>
</update>

利用 set 標(biāo)簽可以實現(xiàn)動態(tài)更新

6. foreach

平時開發(fā)中我們會遇到下面這樣的查詢語句

 select * from student where sid in (1, 2, 3)

這個時候可以用 foreach 標(biāo)簽做到這樣的效果

public interface StudentDao {
    public List<Student> selectPartStudent(@Param("sids") Integer[] sids);
}
<select id="selectPartStudent" resultMap="resultMap1">
    select * from student where sid in
    <foreach collection="sids" 
             item="id" 
             separator="," 
             open="("
             close=")">
        #{id}
    </foreach>
</select>

最終生成的 SQL 語句
select * from student where sid in ( ? , ? , ? ) 

對上面 foreach 標(biāo)簽里面的幾個屬性做下解釋
collection="ids":接口上傳過來的數(shù)值或list集合或者map集合都可以
item="id":設(shè)定遍歷集合或數(shù)組里的每一個值的迭代變量
separator=",": 因為要構(gòu)造出 (1,2,3)這種樣子的字符串,設(shè)定中間的分隔符
open="(": 因為要構(gòu)造出 (1,2,3)這種樣子的字符串,設(shè)定前綴的符號(
close=")":因為要構(gòu)造出 (1,2,3)這種樣子的字符串,設(shè)計結(jié)尾的后綴)
index:還有這個屬性,數(shù)組或list集合的時候,設(shè)置索引變量,如果是Map集合就是map的key的迭代變量,這里的例子用不著這個。

7. bind

這個標(biāo)簽作用就是將OGNL標(biāo)簽里的值,進(jìn)行二次加工,在綁定到另一個變量里,供其他標(biāo)簽使用。

例如在用到模糊查詢時
方案一

public interface StudentDao {
    public List<Student> selectStudentsByName(@Param("name") String name);
}
<select id="selectStudentsByName" resultMap="resultMap1">
    select * from student where sname like #{name}
</select>

這時在調(diào)用的時候需要這么來寫

mapper.selectStudentsByName("%m%");

方案二
用 bind 標(biāo)簽來實現(xiàn)就簡單多了

<select id="selectStudentsByName" resultMap="resultMap1">
    <bind name="_name" value="'%' + name + '%'"></bind>
    select * from student where sname like #{_name}
</select>

這個時候調(diào)用就方便多了,模糊查詢的效果同上面第一種方案一樣

mapper.selectStudentsByName("m");

九、MyBatis一級緩存

MyBatis 包含一個非常強(qiáng)大的查詢緩存特性,它可以非常方便地配置和定制。MyBatis 3 中的緩存實現(xiàn)的很多改進(jìn)都已經(jīng)實現(xiàn)了,使得它更加強(qiáng)大而且易于配置。
Mybatis 和 Hibernate一樣,也有一級和二級緩存,同樣默認(rèn)開啟的只有一級緩存,二級緩存也需要手動配置開啟,我們先看看一級緩存。

一級緩存又被稱為 session 級別的緩存,mybatis一直默認(rèn)是開啟的,每個與數(shù)據(jù)庫的連接會話都有各自自己的緩存,這些一級緩存之間是不能通信的,是相互獨立的緩存空間!

總結(jié): 一級緩存其實就是一個 Map,一個 session 對應(yīng)一個 Map,所以說兩個不同的 session 之間的一級緩存不共享,Map 里的 key 就是主鍵 id。所以查詢對象的時候,先查緩存,緩存的 Map 對象中找不到時才會發(fā)送SQL語句,查詢出來后會放到對應(yīng)的緩存 Map 里。通過調(diào)用 session.clearCache() 可以用來清空緩存,同時增刪改也會刷新緩存。


十、MyBatis二級緩存

Mybatis默認(rèn)情況下二級緩存是關(guān)閉的,需要手工的配置開啟,在開啟之前,我們先說說二級緩存的基本知識點:

    1. 二級緩存又稱為全局緩存,它是基于 namespace 級別的緩存,一個名稱空間對應(yīng)一個二級緩存,也就是說一般情況下同一個映射文件中的查詢都共享一個共同的二級緩存空間。
    1. 一級緩存的生命周期隨著一次會話 session 的關(guān)閉而清空,開啟二級緩存的情況下,一級緩存里的數(shù)據(jù),在清空或者提交之前會轉(zhuǎn)存到二級緩存的空間中繼續(xù)存在。
    1. 當(dāng)一次會話 sqlsession 的緩存里如果存放著兩個不同類型的對象,比如 Grade 和 Student 對象,當(dāng)一級緩存清空之前,開起二級緩存的情況下,它們兩個對象會分別存入各自的名稱空間的二級緩存空間中。直白的說就是一級緩存中兩個對象是放在同一Map對象(緩存就是Map對象),在二級緩存中兩個對象是分別放在兩個獨立的Map對象里的(各自的緩存空間里)。

開啟二級緩存步驟

① 設(shè)置全局變量 cacheEnabled 設(shè)置為 true

設(shè)置參數(shù) 描述 有效值 默認(rèn)值
cacheEnabled 全局地開啟或關(guān)閉配置文件中的所有映射器已經(jīng)配置的任何緩存 true/false true
<?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>
    <settings>
        ...
        <!-- 開啟二級緩存 -->
        <setting name="cacheEnabled" value="true"/>
    </settings>
    ...
</configuration>

② 在映射文件中添加一個標(biāo)簽<cache/>

<?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.ogemray.dao.StudentDao">
    <cache />
    ...
</mapper>

<cache/>這個簡單語句的效果如下:

  • 映射語句文件中的所有 select 語句將會被緩存。
  • 映射語句文件中的所有 insert,update 和 delete 語句會刷新緩存。
  • 緩存會使用 Least Recently Used(LRU,最近最少使用的)算法來收回。
  • 根據(jù)時間表(比如 no Flush Interval,沒有刷新間隔),緩存不會以任何時間順序來刷新。
  • 緩存會存儲列表集合或?qū)ο?無論查詢方法返回什么)的 1024 個引用。
  • 緩存會被視為是 read/write(可讀/可寫)的緩存,意味著對象檢索不是共享的,而且可以安全地被調(diào)用者修改,而不干擾其他調(diào)用者或線程所做的潛在修改。

<cache/>這個標(biāo)簽中還有很多與緩存有關(guān)的屬性:
eviction:可用的收回策略有:

  • LRU – 最近最少使用的,移除最長時間不被使用的對象。
  • FIFO – 先進(jìn)先出,按對象進(jìn)入緩存的順序來移除它們。
  • SOFT – 軟引用,移除基于垃圾回收器狀態(tài)和軟引用規(guī)則的對象。
  • WEAK – 弱引用,更積極地移除基于垃圾收集器狀態(tài)和弱引用規(guī)則的對象。

默認(rèn)的是 LRU。

flushInterval:刷新間隔,可以被設(shè)置為任意的正整數(shù),而且它們代表一個合理的毫秒形式的時間段。默認(rèn)情況是不設(shè)置,也就是沒有刷新間隔,緩存僅僅調(diào)用語句時刷新。
size:引用數(shù)目,可以被設(shè)置為任意正整數(shù),要記住你緩存的對象數(shù)目和你運(yùn)行環(huán)境的可用內(nèi)存資源數(shù)目。默認(rèn)值是 1024。
readOnly:(只讀)屬性可以被設(shè)置為 true 或 false。只讀的緩存會給所有調(diào)用者返回緩存對象的相同實例,因此這些對象不能被修改,這提供了很重要的性能優(yōu)勢??勺x寫的緩存會返回緩存對象的拷貝(通過序列化) 。這會慢一些,但是安全,因此默認(rèn)是 false。

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

這個更高級的配置創(chuàng)建了一個 FIFO 緩存,并每隔 60 秒刷新,存數(shù)結(jié)果對象或列表的 512 個引用,而且返回的對象被認(rèn)為是只讀的,因此在不同線程中的調(diào)用者之間修改它們會導(dǎo)致沖突。

③ Mybatis的二級緩存使用的序列化接口,所以,我們要使用二級緩存,我們的JavaBean就必須實現(xiàn)序列化接口

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    ...
}

代碼測試

SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentDao mapper1 = sqlSession1.getMapper(StudentDao.class);
Student student1 = mapper1.selectOneById(1);
System.out.println(student1);
sqlSession1.close();

SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentDao mapper2 = sqlSession2.getMapper(StudentDao.class);
Student student2 = mapper2.selectOneById(1);
System.out.println(student2);
sqlSession2.close();
DEBUG ==>  Preparing: select * from student where sid = ? 
DEBUG ==> Parameters: 1(Integer)
DEBUG <==      Total: 1
Student [id=1, name=Mike, birthday=Mon Nov 05 00:00:00 CST 2018, grade=null]

DEBUG Cache Hit Ratio [com.ogemray.dao.StudentDao]: 0.5
Student [id=1, name=Mike, birthday=Mon Nov 05 00:00:00 CST 2018, grade=null]

從上面可以看出,用兩個不同的 session 查詢同個對象只發(fā)送一次 SQL語句。注意,在用另一個 session 查詢前先將上個 session 關(guān)閉,這樣才會將一級緩存里面的數(shù)據(jù)放到二級緩存里面。

注意
① 設(shè)置 useCache=false 可以禁用當(dāng)前 select 語句的二級緩存,即每次查詢都會發(fā)出 sql 去查詢,默認(rèn)情況是 true,即該sql使用二級緩存。

<select id="selectOneById" resultMap="resultMap1" useCache="false">
    select * from student where sid = #{id}
</select>

② 清空緩存 flushCache 屬性

  • flushCache默認(rèn)為false,表示任何時候語句被調(diào)用,都不會去清空本地緩存和二級緩存。
  • useCache默認(rèn)為true,表示會將本條語句的結(jié)果進(jìn)行二級緩存。
  • 在insert、update、delete語句時: flushCache默認(rèn)為true,表示任何時候語句被調(diào)用,都會導(dǎo)致本地緩存和二級緩存被清空。 useCache屬性在該情況下沒有。例如如果 update 的時候如果 flushCache="false",則當(dāng)你更新后,查詢的數(shù)據(jù)數(shù)據(jù)還是老的數(shù)據(jù)。

如果沒有去配置flushCache、useCache,那么默認(rèn)是啟用緩存的

<select id="selectOneById" 
        resultMap="resultMap1" 
        useCache="true"
        flushCache="false">
    select * from student where sid = #{id}
</select>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容