Mybatis-初見

介紹

  • MyBatis 是一款優秀的持久層框架;
  • 它支援自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎所有的 JDBC 程式碼以及設置參數和獲取結果集的工作。MyBatis 可以通過簡單的 XML 或註解來配置和映射原始類型、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為資料庫中的記錄。

示例

搭建環境

<!--導入依賴-->
<dependencies>
    <!--mysqlq驅動-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.12</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.4</version>
    </dependency>
</dependencies>

創建一個模組

編寫mybatis的核心配置文件 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "//mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?userSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

編寫mybatis工具類

//sqlSessionFactory --> sqlSession
public class MybatisUtils {

    static SqlSessionFactory sqlSessionFactory = null;

    static {
        try {
            //使用Mybatis第一步 :獲取sqlSessionFactory對象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //既然有了 SqlSessionFactory,顧名思義,我們可以從中獲得 SqlSession 的實例.
    // SqlSession 提供了在資料庫執行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

編寫程式碼

namespace中的包名要和Dao/Mapper介面的包名一致

  • id:就是對應的namespace中的方法名;
  • resultType : Sql語句執行的返回值;
  • parameterType : 參數類型;
public interface UserDao {
    public List<User> getUserList();
}


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

<!--namespace=綁定一個指定的Dao/Mapper介面-->
<mapper namespace="com.zwt.dao.UserDao">
    <select id="getUserList" resultType="com.kuang.pojo.User">
    select * from USER
  </select>
</mapper>

    
        @Test
    public void test(){

        //1.獲取SqlSession對象
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //2.執行SQL
        // 方式一:getMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();
        for (User user : userList) {
            System.out.println(user);
        }

        //關閉sqlSession
        sqlSession.close();
    }

CURD

注意:增刪改查一定要提交事務:

sqlSession.commit();

萬能Map

假設,我們的實體類,或者資料庫中的表,欄位或者參數過多,我們應該考慮使用Map!

//用萬能Map插入用戶
public void addUser2(Map<String,Object> map);

<!--對象中的屬性可以直接取出來 傳遞map的key-->
<insert id="addUser2" parameterType="map">
    insert into user (id,name,password) values (#{userid},#{username},#{userpassword})
</insert>

Map傳遞參數,直接在sql中取出key即可! 【parameter=「map」】

對象傳遞參數,直接在sql中取出對象的屬性即可! 【parameter=「Object」】

只有一個基本類型參數的情況下,可以直接在sql中取到

多個參數用Map , 或者註解!

配置解析

  • mybatis-config.xml
  • Mybatis的配置文件包含了會深深影響MyBatis行為的設置和屬性資訊。
configuration(配置)
    properties(屬性)
    settings(設置)
    typeAliases(類型別名)
    typeHandlers(類型處理器)
    objectFactory(對象工廠)
    plugins(插件)
    environments(環境配置)
    	environment(環境變數)
    		transactionManager(事務管理器)
    		dataSource(數據源)
    databaseIdProvider(資料庫廠商標識)
    mappers(映射器)

環境配置 environments

MyBatis 可以配置成適應多種環境

不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境

學會使用配置多套運行環境!

MyBatis默認的事務管理器就是JDBC ,連接池:POOLED

屬性 properties

我們可以通過properties屬性來實現引用配置文件

這些屬性可以在外部進行配置,並可以進行動態替換。

你既可以在典型的 Java 屬性文件中配置這些屬性,也可以在 properties 元素的子元素中設置。【db.poperties】

類型別名 typeAliases

  • 類型別名可為 Java 類型設置一個縮寫名字。 它僅用於 XML 配置.
  • 意在降低冗餘的全限定類名書寫。
<!--可以給實體類起別名-->
<typeAliases>
    <typeAlias type="com.zwt.pojo.User" alias="User"/>
</typeAliases>

<typeAliases>
    <package name="com.kuang.pojo"/>
</typeAliases>

@Alias("author")
public class Author {
    ...
}

其他配置

映射器 mappers

MapperRegistry:註冊綁定我們的Mapper文件;

方式一:【推薦使用】

<!--每一個Mapper.xml都需要在MyBatis核心配置文件中註冊-->
<mappers>
    <mapper resource="com/zwt/dao/UserMapper.xml"/>
</mappers>

方式二:使用class文件綁定註冊

<!--每一個Mapper.xml都需要在MyBatis核心配置文件中註冊-->
<mappers>
    <mapper class="com.zwt.dao.UserMapper"/>
</mappers>


注意點:

介面和他的Mapper配置文件必須同名
介面和他的Mapper配置文件必須在同一個包下

方式三:使用包掃描進行注入

<mappers>
    <package name="com.kuang.dao"/>
</mappers>

作用域和生命周期

SqlSessionFactoryBuilder:

  • 一旦創建了SqlSessionFactory,就不再需要它了
  • 局部變數

SqlSessionFactory:

  • 說白了就可以想像為:資料庫連接池
  • qlSessionFactory一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建一個實例
  • 因此SqlSessionFactory的最佳作用域是應用作用域(ApplocationContext)。
  • 最簡單的就是使用單例模式或靜態單例模式。

SqlSession:

  • 連接到連接池的一個請求
  • SqlSession 的實例不是執行緒安全的,因此是不能被共享的,所以它的最佳的作用域是請求或方法作用域。
  • 用完之後需要趕緊關閉,否則資源被佔用!

分頁

用分頁是為了減少數據的處理量

使用Limit分頁

//分頁
List<User> getUserByLimit(Map<String,Integer> map);


<!--分頁查詢-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from user limit #{startIndex},#{pageSize}
</select>


    @Test
    public void getUserByLimit(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("startIndex",1);
        map.put("pageSize",2);
        List<User> list = mapper.getUserByLimit(map);
        for (User user : list) {
            System.out.println(user);
        }
    }

RowBounds分頁

//分頁2
List<User> getUserByRowBounds();

<!--分頁查詢2-->
<select id="getUserByRowBounds">
    select * from user limit #{startIndex},#{pageSize}
</select>

    
    public void getUserByRowBounds(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        //RowBounds實現
        RowBounds rowBounds = new RowBounds(1, 2);
        //通過Java程式碼層面實現分頁
        List<User> userList = sqlSession.selectList("com.zwt.dao.UserMapper.getUserByRowBounds", null, rowBounds);
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }

分頁插件

註解開發

//註解在介面上實現
@Select("select * from user")
List<User> getUsers();


//需要在核心配置文件中綁定介面
<mappers>
    <mapper class="com.zwt.dao.UserMapper"/>
</mappers>

本質:反射機制實現

底層:動態代理

MyBatis詳細執行流程

註解CURD

//方法存在多個參數,所有的參數前面必須加上@Param("id")註解
@Delete("delete from user where id = ${uid}")
int deleteUser(@Param("uid") int id);

關於@Param( )註解

  • 基本類型的參數或者String類型,需要加上
  • 引用類型不需要加
  • 如果只有一個基本類型的話,可以忽略,但是建議大家都加上
  • 我們在SQL中引用的就是我們這裡的@Param()中設定的屬性名

多對一and一對多(處理)

多個學生一個老師;

多對一

alter table student ADD CONSTRAINT fk_tid foreign key (tid) references teacher(id)

測試環境搭建:

  1. 導入lombok
  2. 新建實體類Teacher,Student
  3. 建立Mapper介面
  4. 建立Mapper.xml文件
  5. 在核心配置文件中綁定註冊我們的Mapper介面或者文件 【方式很多,隨心選】
  6. 測試查詢是否能夠成功
@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}


@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}

//按照查詢嵌套處理
<!--
     思路:
        1. 查詢所有的學生資訊
        2. 根據查詢出來的學生的tid尋找特定的老師 (子查詢)
    -->
<select id="getStudent" resultMap="StudentTeacher">
    select * from student
</select>
<resultMap id="StudentTeacher" type="student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--複雜的屬性,我們需要單獨出來 對象:association 集合:collection-->
    <collection property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id}
</select>

    
//按照結果嵌套處理
    <!--按照結果進行查詢-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid , s.name sname, t.name tname
        from student s,teacher t
        where s.tid=t.id
    </select>
    <!--結果封裝,將查詢出來的列封裝到對象屬性中-->
    <resultMap id="StudentTeacher2" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <result property="name" column="tname"></result>
        </association>
    </resultMap>

  • 子查詢 (按照查詢嵌套)
  • 聯表查詢 (按照結果嵌套)

一對多

<!--按結果嵌套查詢-->
<select id="getTeacher" resultMap="StudentTeacher">
    SELECT s.id sid, s.name sname,t.name tname,t.id tid FROM student s, teacher t
    WHERE s.tid = t.id AND tid = #{tid}
</select>
<resultMap id="StudentTeacher" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--複雜的屬性,我們需要單獨處理 對象:association 集合:collection
    javaType=""指定屬性的類型!
    集合中的泛型資訊,我們使用ofType獲取
    -->
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

小結

  1. 關聯 – association 【多對一】
  2. 集合 – collection 【一對多】
  3. javaType & ofType
    1. JavaType用來指定實體類中的類型
    2. ofType用來指定映射到List或者集合中的pojo類型,泛型中的約束類型

動態SQL

什麼是動態SQL:動態SQL就是根據不同的條件生成不同的SQL語句

所謂的動態SQL,本質上還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯程式碼

例如IF

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title!=null">
            and title = #{title}
        </if>
        <if test="author!=null">
            and author = #{author}
        </if>
    </where>
</select>

            
 <sql id="if-title-author">
    <if test="title!=null">
        title = #{title}
    </if>
    <if test="author!=null">
        and author = #{author}
    </if>
</sql>

        
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>
        

動態SQL就是在拼接SQL語句,我們只要保證SQL的正確性,按照SQL的格式,去排列組合就可以了

建議:

  • 先在Mysql中寫出完整的SQL,再對應的去修改成我們的動態SQL實現通用即可

MyBatis快取

查詢 : 連接資料庫,耗資源

一次查詢的結果,給他暫存一個可以直接取到的地方 –> 記憶體:快取

我們再次查詢的相同數據的時候,直接走快取,不走資料庫了

  • MyBatis包含一個非常強大的查詢快取特性,它可以非常方便的訂製和配置快取,快取可以極大的提高查詢效率。
  • MyBatis系統中默認定義了兩級快取:一級快取和二級快取
  • 默認情況下,只有一級快取開啟(SqlSession級別的快取,也稱為本地快取)
  • 二級快取需要手動開啟和配置,他是基於namespace級別的快取。
  • 為了提高可擴展性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來定義二級快取。

一級快取

  • 一級快取也叫本地快取:SqlSession
    • 與資料庫同一次會話期間查詢到的數據會放在本地快取中
    • 以後如果需要獲取相同的數據,直接從快取中拿,沒必要再去查詢資料庫
    @Test
    public void test1() {
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);

        System.out.println("=====================================");

        User user2 =  mapper.getUserById(1);
        System.out.println(user2 == user);
    }

//測試,開啟日誌後可以看到  僅僅連接了一次JDBC

快取失效的情況:

  1. 查詢不同的東西
  2. 增刪改操作,可能會改變原來的數據,所以必定會刷新快取
  3. 查詢不同的Mapper.xml
  4. 手動清理快取(sqlSession.clearCache();)

二級快取

  • 二級快取也叫全局快取,一級快取作用域太低了,所以誕生了二級快取

  • 基於namespace級別的快取,一個名稱空間,對應一個二級快取

  • 工作機制

    • 一個會話查詢一條數據,這個數據就會被放在當前會話的一級快取中
    • 如果會話關閉了,這個會話對應的一級快取就沒了;但是我們想要的是,會話關閉了,一級快取中的數據被保存到二級快取中
    • 新的會話查詢資訊,就可以從二級快取中獲取內容
    • 不同的mapper查詢出的數據會放在自己對應的快取(map)中

一級快取開啟(SqlSession級別的快取,也稱為本地快取)

  • 二級快取需要手動開啟和配置,他是基於namespace級別的快取。
  • 為了提高可擴展性,MyBatis定義了快取介面Cache。我們可以通過實現Cache介面來定義二級快取。

步驟:

<!--顯示的開啟全局快取-->
<setting name="cacheEnabled" value="true"/>

<!--在當前Mapper.xml中使用二級快取-->
<cache
       eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

小結:

  • 只要開啟了二級快取,在同一個Mapper下就有效
  • 所有的數據都會放在一級快取中
  • 只有當前會話提交,或者關閉的時候,才會提交到二級快取中

快取原理

注意:

  • 只有查詢才有快取,根據數據是否需要快取(修改是否頻繁選擇是否開啟)useCache=「true」
    <select id="getUserById" resultType="user" useCache="true">
        select * from user where id = #{id}
    </select>

自定義快取-ehcache

Ehcache是一種廣泛使用的開源Java分散式快取。主要面向通用快取

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

//在mapper中指定使用我們的ehcache快取實現
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>