Mybatis

目錄
????1 JDBC
????2 Mybaits概述
????3 Mybatis擴(kuò)展
????4 Mybatis的加載和緩存
????5 Mybatis注解開發(fā)
????6 Mybatis分頁
????7 常見問題匯總

參考資料:
·《Java使用教程 第3版》
· JavaG
· CSDN

1 JDBC

? ? JDBC是Java DataBase Connectivity的縮寫法竞,它是一種可用于執(zhí)行SQL語句的Java API挽拂,其中包含跨平臺的數(shù)據(jù)庫訪問方法,為數(shù)據(jù)庫應(yīng)用開發(fā)人員提供了一種標(biāo)準(zhǔn)的應(yīng)用程序編程接口,屏蔽了具體數(shù)據(jù)庫的差異,如圖1-1所示粗合。當(dāng)Java程序訪問數(shù)據(jù)庫時(shí),由 JDBC API接口調(diào)用相應(yīng)數(shù)據(jù)庫的API實(shí)現(xiàn)來訪問數(shù)據(jù)庫,從而無須改變Java程序就能訪問不同的數(shù)據(jù)庫。

圖1-1 JDBC原理

1.1 JDBC實(shí)現(xiàn)

????不同的數(shù)據(jù)庫提供不同的JDBC實(shí)現(xiàn),如圖1-2所示,JDBC的實(shí)現(xiàn)包括三部分。

圖 1-2 JDBC實(shí)現(xiàn)

· JDBC驅(qū)動(dòng)管理器:對應(yīng) java. sql DriverManager類,它負(fù)責(zé)注冊特定JBC驅(qū)動(dòng)器,以及根據(jù)驅(qū)動(dòng)器建立與數(shù)據(jù)庫的連接

· JDBC驅(qū)動(dòng)器API:其中最主要的是java. gl. Driver接口

· JDBC驅(qū)動(dòng)器:由數(shù)據(jù)庫供應(yīng)商或其他第三方提供,也稱為JDBC驅(qū)動(dòng)程序鹿榜。它們實(shí)現(xiàn)了JDBC驅(qū)動(dòng)器API( Driver接口),負(fù)責(zé)與特定的數(shù)據(jù)庫連接命爬。JDBC驅(qū)動(dòng)器可以注冊到JBC驅(qū)動(dòng)管理器中。不同數(shù)據(jù)庫提供的JDBC驅(qū)動(dòng)器也不同。

1.2 訪問數(shù)據(jù)庫步驟

(1) 加載驅(qū)動(dòng)程序狭莱。調(diào)用DriverManager類的registerDriver()方法用來注冊驅(qū)動(dòng)程序類的實(shí)例僵娃。

(2) 建立連接。調(diào)用DrierManager類的getConnection()方法得到一個(gè)與數(shù)據(jù)庫的連接腋妙,返回一個(gè)Connection對象默怨。

(3) 操作數(shù)據(jù)庫。 調(diào)用Connection對象的createStatement()骤素、prepareStatement()等方法執(zhí)行SQL語句匙睹,返回結(jié)果集ResultSet。

(4) 斷開連接济竹。

1.3 常用接口

1.3.1 Statement接口

????調(diào)用 Connection對象的 createStatement()方法創(chuàng)建一個(gè) Statement對象痕檬。 Statement接口的常用方法如下:

(1) boolean execute(String sql) throws SQLExceptior:執(zhí)行給定的SoL語句

(2) int executeUpdate(String sql) throws SQLException:執(zhí)行給定SQL語句,該語句可能為INSERT、 UPDATE或 DELETE語句,或者不返回任何內(nèi)容的SQL語句(如 SQL DDL語句)

(3) Resultset execute Query( String sql) throws SQLException:執(zhí)行給定的SQL語句,該語句返回單個(gè) Resultset對象送浊。

(4) void addBatch( (String sql) throws SQLException:將給定的SOL命令添加到此Statement對象的當(dāng)前命令列表中梦谜。通過調(diào)用方法 executeBatch可以批量執(zhí)行此列表中的命令

(5) int[] executeBatch() throws SQLException:將一批命令提交給數(shù)據(jù)庫來執(zhí)行,如果全部命令執(zhí)行成功,則返回更新計(jì)數(shù)組成的數(shù)組。

1.3.2 PreparedStatement接口

????Java提供了一個(gè) Statement接口的子接口 PreparedStatement,兩者的功能相似,但當(dāng)某SQL指令被執(zhí)行多次時(shí), PreparedStatement的效率要比 Statement高,而且 PreparedStatement還可以給SQL指令傳遞參數(shù)袭景。調(diào)用 Connection對象的 prepareStatement()方法來得到 PreparedStatement對象唁桩。PreparedStatement對象所代表的SQL語句中的參數(shù)用問號(?)來表示。調(diào)用 PreparedStatement對象的 setXXX()方法來設(shè)置這些參數(shù)耸棒。PreparedStatement接口的常用方法如下:

(1) void set Boolean(int parameterIndex, boolean x) throws SQLException:將指定參數(shù)設(shè)置為給定boolean值荒澡。parameterIndex的第一個(gè)參數(shù)是1,第二個(gè)參數(shù)是2... ...x 是參數(shù)值。

(2) void setInt(int parameterIndex, int x) throws SQLException::將指定參數(shù)設(shè)置為給定int值榆纽。

(3) void setFloat(int parameterIndex., float x) throws SQLException:將指定參數(shù)設(shè)置為給定float值仰猖。

(4) void set Double(int parameterIndex, double x) throws SQLException:將指定參數(shù)設(shè)置為給定double值。

(5) void setString(int parameterIndex, String x) throws SQLException:將指定參數(shù)設(shè)置為給定String值奈籽。

(6) void setDate(int parameterIndex, Date x) throws SQLException:使用運(yùn)行應(yīng)用程序的虛擬機(jī)的默認(rèn)時(shí)區(qū)將指定參數(shù)設(shè)置為給定java.sql.Date值饥侵。

1.3.3 ResultSet接口

????當(dāng)使用 Statement和 PreparedStatement中的 executeQuery方法來執(zhí)行 select查詢指令時(shí),查詢的結(jié)果被放在結(jié)果集 Resultset中。Resultset接口的常用方法如下:

(1) String getString(int columnIndex) throws SQLException:獲取此 Resultset對象的當(dāng)前行中指定列的值,參數(shù) columnIndex代表字段的索引位置衣屏。

(2) String getString(String columnLabel) throws SQLException:獲取此 Resultset對象的當(dāng)前行中指定列的值,參數(shù) columnLabel代表字段值躏升。

(3) int getInt (int columnIndex) throws SQLException:獲取此 ResultSet對象的當(dāng)前行中指定列的值,參數(shù) columnIndey代表字段值。

(4) int getInt(String column Label) throws SQLException:獲取此 Resultset對象的當(dāng)前行中指定列的值,參數(shù) columnLabel代表字段值狼忱。

(5) boolean absolute(int row) throws SQLException:將光標(biāo)移動(dòng)到此 Resultset對象的給定行編號烦衣。

(6) boolean previous() throws SQLException:將光標(biāo)移動(dòng)到此 Resultset對象的上一行遇伞。

(7) boolean first() throws SQLException:將光標(biāo)移動(dòng)到此 Resultset對象的第一行。

(8) boolean last() throws SQLException:將光標(biāo)移動(dòng)到此 Resultset對象的最后一行。

(9) boolean next() throws SQLException:將光標(biāo)移到下一行, Resultset光標(biāo)最初位于第一行之前,第一次調(diào)用next0方法使第一行成為當(dāng)前行迫像。

1.4 JDBC Demo

String username = "username=' OR 1=1 -- ";

String password = "12345";

// String sql = "SELECT id,username FROM user_table WHERE " +

// "username='" + username + "'AND " + "password='"

// + password + "'";

//優(yōu)點(diǎn)在于,可以進(jìn)行預(yù)編譯

String sql = "SELECT id,username FROM user_table WHERE username=? AND password=?";

Class.forName("com.mysql.jdbc.Driver");

Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");

PreparedStatement stat = con.prepareStatement(sql);

stat.setString(1, username);

stat.setString(2, password);

System.out.println(stat.toString());

ResultSet rs = stat.executeQuery();

while (rs.next()) {

????String id = rs.getString(1);

????String name = rs.getString(2);

System.out.println("id:" + id + "---name:" + name);

}

1.5 事務(wù)處理

????事務(wù)處理是由單一的邏輯單位完成的一系列操作,它由一系列對數(shù)據(jù)庫的操作組成遇八。事務(wù)處理在數(shù)據(jù)庫系統(tǒng)中主要用來實(shí)現(xiàn)數(shù)據(jù)完整性,所有遵守JDBC規(guī)范的JDBC驅(qū)動(dòng)程序都支持事務(wù)處理敞恋。當(dāng)在一個(gè)事務(wù)中執(zhí)行多個(gè)操作時(shí),只有所有操作成功才意味著整個(gè)事務(wù)成功。只要有一個(gè)操作失敗,整個(gè)事務(wù)就失敗,該事務(wù)會回滾( rollback)到最初的狀態(tài)。

????當(dāng)一個(gè)連接對象被創(chuàng)建時(shí),默認(rèn)情況下事務(wù)被設(shè)置為自動(dòng)提交狀態(tài)灶泵。這意味著每次執(zhí)行一條SQL語句時(shí),如果執(zhí)行成功,就會自動(dòng)調(diào)用 commit()方法向數(shù)據(jù)庫提交,也就不能再回滾了育八。為了將多條SQL語句作為一個(gè)事務(wù)執(zhí)行,可以設(shè)置 Connection對象的 setAutoCommit(false)。然后在所有的SQL語句成功執(zhí)行后,顯式調(diào)用 Connection對象的commit()方法來提交事務(wù),或者在執(zhí)行出錯(cuò)時(shí)調(diào)用Connection對象的 rollback()方法來回滾事務(wù)赦邻。

2 Mybatis概述

2.1 簡介

· Mybatis 是一個(gè)使用java編寫的持久層框架髓棋。它封裝了 JDBC?,使開發(fā)者只需要關(guān)注?sql 語句惶洲,而無需關(guān)注注冊驅(qū)動(dòng)按声、創(chuàng)建連接、創(chuàng)建 Statement 等繁雜的過程恬吕。

· ORM(Object Relational Mapping)對象關(guān)系映射儒喊。簡單地說,就是把數(shù)據(jù)庫表和實(shí)體類及實(shí)體類的屬性對應(yīng)起來币呵,讓我們可以通過操作實(shí)體類來操作數(shù)據(jù)庫表怀愧。

2.2 maven依賴

2.2.1 mybatis

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
????<groupId>org.mybatis</groupId>
????<artifactId>mybatis</artifactId>
????<version>3.4.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
????<groupId>mysql</groupId>
????<artifactId>mysql-connector-java</artifactId>
????<version>5.1.21</version>
</dependency>

相應(yīng)定義的配置文件:
· UserMapper.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="cn.ykf.mapper.UserMapper">

? ? <!-- 配置查詢所有用戶 -->

? ? <select id="listAllUsers" resultType="cn.ykf.pojo.User">

? ? ? ? SELECT * FROM user

? ? </select>

</mapper>

· mybatis.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>

? ? ? ? <property name="driver" value="com.mysql.jdbc.Driver"/>

? ? ? ? <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis"/>

? ? ? ? <property name="username" value="root"/>

? ? ? ? <property name="password" value="root"/>

? ? </properties>

? ? <!--配置環(huán)境-->

? ? <environments default="development">

? ? ? ? <environment id="development">

? ? ? ? ? ? <!-- 配置事務(wù)類型 -->

? ? ? ? ? ? <transactionManager type="JDBC"></transactionManager>

? ? ? ? ? ? <!-- 配置數(shù)據(jù)源(連接池) -->

? ? ? ? ? ? <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="UserMapper.xml"/>

? ? </mappers>

</configuration>

· 注意事項(xiàng)

????· sql中的#{username}等必須與對應(yīng)類的屬性名一致
????· 映射配置文件的 mapper 標(biāo)簽?namespace 屬性的取值必須是 mapper 接口的全限定類名
? ??·?映射配置文件的操作配置,id 屬性的取值必須是 mapper 接口的方法名

2.2.2 springboot整合mybatis

<parent>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-starter-parent</artifactId>
????<version>1.5.2.RELEASE</version>
????<relativePath?/>?<!-- lookup parent from repository -->
</parent>
<dependencies>
????<dependency>
????????<groupId>org.springframework.boot</groupId>
????????<artifactId>spring-boot-starter</artifactId>
????</dependency>
????<dependency>
????????<groupId>org.mybatis.spring.boot</groupId>
????????<artifactId>mybatis-spring-boot-starter</artifactId>
????????<version>1.1.1</version>
????</dependency>
????<dependency>
????????<groupId>mysql</groupId>
????????<artifactId>mysql-connector-java</artifactId>
????????<version>5.1.21</version>
????</dependency>
</dependencies>

2.3 原理分析

? ? 以mybatis框架情況為例講解余赢。

2.3.1 mybatis調(diào)用的主類

public class MybatisTest {

? ? /**

? ? * Mybatis 入門案例

? ? */

? ? @Test

? ? public void testInit() {

? ? ? ? try {

? ? ? ? ? ? // 1. 讀取配置文件

? ? ? ? ? ? InputStream is = Resources.getResourceAsStream("mybatis-config.xml");

? ? ? ? ? ? // 2. 創(chuàng)建 SqlSessionFactory 工廠

? ? ? ? ? ? SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

? ? ? ? ? ? SqlSessionFactory factory = builder.build(is);

? ? ? ? ? ? // 3. 獲取 SqlSession 對象

? ? ? ? ? ? SqlSession sqlSession = factory.openSession();

? ? ? ? ? ? // 4. 使用 SqlSession 創(chuàng)建 Mapper 的代理對象

? ? ? ? ? ? UserMapper mapper = sqlSession.getMapper(UserMapper.class);

? ? ? ? ? ? // 5. 使用代理對象執(zhí)行查詢

? ? ? ? ? ? List<User> users = mapper.findAll();

? ? ? ? ? ? users.forEach(System.out::println);

? ? ? ? ? ? // 6. 釋放資源

? ? ? ? ? ? sqlSession.close();

? ? ? ? ? ? is.close();

? ? ? ? } catch (IOException e) {

? ? ? ? ? ? e.printStackTrace();

? ? ? ? }

? ? }

}

圖 2-1 主類分析

· 構(gòu)建者模式:創(chuàng)建SqlSessionFactory對象
· 工廠模式: 創(chuàng)建SqlSession對象
· 代理模式:? 創(chuàng)建Dao接口實(shí)體類芯义。getMapper(類名.class)方法,使用JDK動(dòng)態(tài)代理創(chuàng)建一個(gè)代理對象妻柒,在對象的InvocationHandler接口實(shí)現(xiàn)類中調(diào)用findAll()方法扛拨。最后使用反射技術(shù)封裝實(shí)體類返回代理對象。

2.3.2 源碼分析

圖 2-2 源碼分析

? ? 在通過代理模式生成代理對象后举塔,調(diào)用方法如listAllUsers()的過程如下:
(1)通過dom4j解析xml文件绑警。根據(jù)mysql數(shù)據(jù)庫的配置信息創(chuàng)建出Connection對象,注冊驅(qū)動(dòng)央渣,建立連接计盒。
(2)獲取預(yù)處理對象PreparedStatement。將UserMapper.xml文件中對應(yīng)方法的sql語句傳入,調(diào)用connection.prepareStatement(sql)獲得PreparedStatement對象芽丹。
(3)執(zhí)行查詢北启。ResultSet resultSet = prepareStatement.executeQuery()
(4)遍歷結(jié)果集,封裝為List<User>返回拔第。

· 注意事項(xiàng)
? ? 在代理對象調(diào)用方法時(shí)咕村,mybatis會將方法需要的sql語句和封裝結(jié)果的實(shí)體類全限定類名(即一個(gè)標(biāo)簽<select>)進(jìn)行組合,定義成一個(gè)MappedStatement對象蚊俺。

3 mybatis擴(kuò)展

3.1 Usermapper.xml中的擴(kuò)展

· POJO包裝
? ? pojo是普通JavaBean懈涛,即屬性+ get/set方法。對應(yīng)還有EJB是企業(yè)JavaBean泳猬,是分布式事務(wù)處理組件批钠。
? ??在開發(fā)中如果想實(shí)現(xiàn)復(fù)雜查詢 泣港,查詢條件不僅包括用戶查詢條件,還包括其它的查詢條件(比如將用戶購買商品信息也作為查詢條件)价匠,這時(shí)可以使用 pojo 包裝對象傳遞輸入?yún)?shù)。即新定義一個(gè)Bean呛每,包含對其他類的引用踩窖。

· 實(shí)體類屬性與數(shù)據(jù)庫表不一致
? ? · 使用別名

SELECT id AS userId, username AS userName, birthday AS userBirthday, sex AS userSex, address AS userAddress FROM user

? ? ?· 使用ResultMap

<resultMap? id="userMap"type="cn.ykf.pojo.User">
????<id property="userId" column="id"/> ????
????<result property="userName" column="username"/> ????
????<result property="userBirthday" column="birthday"/>
????<result property="userAddress" column="address"/>
????<result property="userSex" column="sex"/>
</resultMap>

3.2 Mybatis的連接池

? ? 數(shù)據(jù)源是執(zhí)行具體的數(shù)據(jù)庫的相關(guān)信息,在配置文件中配置的晨横,如mybatis.xml中的<datasource>洋腮。在 Mybatis 中,數(shù)據(jù)源 dataSource 共有三類手形,分別是:

· UNPOOLED?: 不使用連接池的數(shù)據(jù)源啥供。采用傳統(tǒng)的?javax.sql.DataSource?規(guī)范中的連接池,Mybatis 中有針對規(guī)范的實(shí)現(xiàn)

· POOLED?: 使用連接池的數(shù)據(jù)源库糠。采用池的思想

· JNDI?: 使用 JNDI 實(shí)現(xiàn)的數(shù)據(jù)源伙狐,采用服務(wù)器提供的 JNDI 技術(shù)實(shí)現(xiàn),來獲取 DataSource 對象瞬欧,不同的服務(wù)器所能拿到的 DataSource 是不一樣的贷屎。注意,如果不是 Web 或者 Maven 的war工程艘虎,是不能使用?JNDI?的唉侄。

3.3 Mybatis多表查詢

3.3.1 多對一(一對多)

(1)編寫聯(lián)合查詢sql語句。

SELECT U.*, a.id AS aid, a.uid, a.money from account a, user u WHERE a.uid = u.id;

(2)定義Bean野建,包含對相關(guān)實(shí)體類的引用(組合)属划。

(3)定義ResultMap。注意使用<association></association>對其中包含的實(shí)體類進(jìn)行定義候生,該標(biāo)簽 用于一對一映射同眯,其中的?property?屬性表示要關(guān)聯(lián)的屬性,javaType?表示待關(guān)聯(lián)的實(shí)體類的全限定類名唯鸭。

3.3.2 多對多

(1)編寫多對多查詢語句

SELECT u.*, r.id as rid, r.role_name, r.role_desc FROM user u LEFT OUTER JOIN user_role ur ON u.id = ur.uid LEFT OUTER JOIN role r ON ur.rid = r.id;

(2)定義Bean嗽测,包含相關(guān)實(shí)體類的集合引用,如List<User>等肿孵。

(3)定義ResultMap唠粥,注意使用<collection></collection>對其中包含的實(shí)體類進(jìn)行定義,該標(biāo)簽 用于一對一映射停做,其中的?property?屬性表示要關(guān)聯(lián)的屬性晤愧,ofType?表示待關(guān)聯(lián)的實(shí)體類的全限定類名。

3.4 Mybatis攔截器

(1)Mybatis攔截器只能攔截四種類型的接口:Executor蛉腌、StatementHandler官份、ParameterHandler和ResultSetHandler只厘。這是在Mybatis的Configuration中寫死了的,如果要支持?jǐn)r截其他接口就需要我們重寫Mybatis的Configuration舅巷。Mybatis可以對這四個(gè)接口中所有的方法進(jìn)行攔截羔味。

(2)利用攔截器實(shí)現(xiàn)Mybatis分頁的一個(gè)思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語句改成對應(yīng)的分頁查詢Sql語句钠右,之后再調(diào)用StatementHandler對象的prepare方法赋元,即調(diào)用invocation.proceed()

【myBatis】Mybatis中的攔截器_程序員面試經(jīng)驗(yàn)分享-CSDN博客_mybatis攔截器

MyBatis學(xué)習(xí)——第四篇(攔截器和攔截器分頁實(shí)現(xiàn))_huyiju的博客-CSDN博客_分頁攔截器

4 Mybatis的加載與緩存

4.1 加載

4.1.1 延遲加載

· 延遲加載就是在需要用到數(shù)據(jù)時(shí)才進(jìn)行加載,不需要用到數(shù)據(jù)時(shí)就不加載數(shù)據(jù)飒房,延遲加載也稱懶加載搁凸。

· 在一對多多對多的表關(guān)系中,通常情況下我們都是采用延遲加載狠毯。

· 實(shí)現(xiàn)
? ? 在mybatis.xml中配置開啟延遲加載

<!-- 開啟延遲加載 -->

<settings>

? ? <setting name="lazyLoadingEnabled" value="true"/>

? ? <setting name="aggressiveLazyLoading" value="false"/>

</settings>

· 注意事項(xiàng)
????在編寫 Mybatis 的配置文件時(shí)护糖,文檔結(jié)構(gòu)一定不可以隨便寫,一定要按照官方文檔所要求的順序嚼松,比如說:<settings></settings>?標(biāo)簽不可以寫在?<environments></environments>下方嫡良。具體文檔結(jié)構(gòu)見下圖:

圖4-1 mybatis配置文件順序

4.1.2 立即加載

· 立即加載就是不管是否需要數(shù)據(jù),只要一進(jìn)行查詢献酗,就會把相關(guān)聯(lián)的數(shù)據(jù)一并查詢出來皆刺。

· 在多對一一對一的表關(guān)系中,通常情況下我們都是采用立即加載凌摄。

4.2 緩存

4.2.1 一級緩存

????一級緩存是?SqlSession?級別的緩存羡蛾,只要 SqlSession 沒有?flush?或?close,它就會存在锨亏。當(dāng)調(diào)用 SqlSession 的修改痴怨、添加、刪除器予、commit()浪藻、close()、clearCache()等方法時(shí)乾翔,就會清空一級緩存爱葵。

圖4-2 一級緩存

· 第一次發(fā)起查詢用戶 id 為 1 的用戶信息,Mybatis 會先去找緩存中是否有 id 為 1 的用戶信息反浓,如果沒有萌丈,從數(shù)據(jù)庫查詢用戶信息。

· 得到用戶信息雷则,將用戶信息存儲到一級緩存中辆雾。

· 如果?sqlSession?去執(zhí)行 commit 操作(執(zhí)行插入、更新月劈、刪除)度迂,那么 Mybatis 就會清空 SqlSession 中的一級緩存藤乙,這樣做的目的為了讓緩存中存儲的是最新的信息,避免臟讀惭墓。

· 第二次發(fā)起查詢用戶 id 為 1 的用戶信息坛梁,先去找緩存中是否有 id 為 1 的用戶信息,緩存中有腊凶,直接從緩存中獲取用戶信息划咐。

· 注意事項(xiàng)
? ? · Mybatis 默認(rèn)就是使用一次緩存的,不需要配置吭狡。
? ? · 一級緩存中存放的是對象。(一級緩存其實(shí)就是 Map 結(jié)構(gòu)丈莺,直接存放對象)

4.2.2 二級緩存

????二級緩存是?Mapper?映射級別的緩存划煮,多個(gè) SqlSession 去操作同一個(gè) Mapper 映射的 SQL 語句,多個(gè)SqlSession 可以共用二級緩存缔俄,二級緩存是跨 SqlSession 的弛秋。

圖4-3 二級緩存

· 當(dāng)?sqlSession1?去查詢用戶信息的時(shí)候,Mybatis 會將查詢數(shù)據(jù)存儲到二級緩存中俐载。

· 如果?sqlSession3?去執(zhí)行相同 Mapper 映射下的 SQL 語句蟹略,并且執(zhí)行 commit 提交,那么 Mybatis 將會清空該 Mapper 映射下的二級緩存區(qū)域的數(shù)據(jù)遏佣。

· sqlSession2?去查詢與?sqlSession1?相同的用戶信息挖炬,Mybatis 首先會去緩存中找是否存在數(shù)據(jù),如果存在直接從緩存中取出數(shù)據(jù)状婶。

· Mybatis 的二級緩存配置

? ? · mybatis.xml

<settings> <!-- 開啟緩存 --> <setting name="cacheEnabled" value="true"/></settings>

? ? · UserMapper.xml

<mapper namespace="cn.ykf.mapper.UserMapper">

????<!-- 使用緩存 -->
?????<cache/>

</mapper>

? ? · 在需要使用二級緩存的操作上配置?(針對每次查詢都需要最新數(shù)據(jù)的操作意敛,要設(shè)置成?useCache="false",禁用二級緩存)

<select id="listAllUsers" resultMap="UserWithAccountsMap" useCache="true"> ????SELECT * FROM user
</select>

· 注意事項(xiàng)
? ? · 當(dāng)我們使用二級緩存的時(shí)候膛虫,所緩存的類一定要實(shí)現(xiàn)?java.io.Serializable?接口草姻,這樣才可以使用序列化的方式來保存對象。
? ? · 由于是序列化保存對象稍刀,所以二級緩存中存放的是數(shù)據(jù)撩独,而不是整個(gè)對象。

5 Mybatis注解開發(fā)

? ? 常用注解表:

圖5-1 常用注解

5.1 單表CURD

public interface UserMapper {
????/** * 查詢所有用戶
?????* *
????@return
?????*/
?????@Select("SELECT * FROM user")
?????@Results(id = "UserMap",value = {
?????????@Result(id = true,property = "userId",column = "id"),
?????????@Result(property = "userName",column = "username"),
?????????@Result(property = "userBirthday",column = "birthday"),
?????????@Result(property = "userSex",column = "sex"),
?????????@Result(property = "userAddress",column = "address"), })
?????List<User> listAllUsers();
?????/** * 添加用戶
?????* *
????@param user
?????*
?????@return 成功返回1账月,失敗返回0
?????*/
?????@Insert("INSERT INTO user(username,birthday,sex,address) VALUES(#{username},#{birthday},#{sex},#{address})")
?????@ResultMap("UserMap")
?????int saveUser(User user);

5.2 多對一(一對多)

public interface AccountMapper {

/**

? ? * 查詢所有賬戶综膀,并查詢所屬用戶,采用立即加載

? ? *

? ? * @return

? ? */

? ? @Select("SELECT * FROM account")

? ? @Results(id = "AccountMap", value = {

? ? ? ? ? ? @Result(id = true, property = "id", column = "id"),

? ? ? ? ? ? @Result(property = "uid", column = "uid"),

? ? ? ? ? ? @Result(property = "user", column = "uid",

? ? ? ? ? ? ? ? ? ? one = @One(select = "cn.ykf.mapper.UserMapper.getUserById", fetchType = FetchType.EAGER))

? ? })

? ? List<Account> listAllAccounts();

}

public interface UserMapper {

? ? /**

? ? * 根據(jù)id查詢單個(gè)用戶

? ? *

? ? * @param userId

? ? * @return

? ? */

? ? @Select("SELECT * FROM user WHERE id = #{id}")

? ? User getUserById(Integer userId);

}

5.3 多對多

public interface UserMapper {

? ? /**

? ? * 查詢所有用戶局齿,并且查詢擁有賬戶僧须,采用延遲加載

? ? *

? ? * @return

? ? */

? ? @Select("SELECT * FROM user")

? ? @Results(id = "UserMap", value = {

? ? ? ? ? ? @Result(id = true, property = "userId", column = "id"),

? ? ? ? ? ? @Result(property = "userName", column = "username"),

? ? ? ? ? ? @Result(property = "userBirthday", column = "birthday"),

? ? ? ? ? ? @Result(property = "userSex", column = "sex"),

? ? ? ? ? ? @Result(property = "userAddress", column = "address"),

? ? ? ? ? ? @Result(property = "accounts", column = "id",

? ? ? ? ? ? ? ? ? ? many = @Many(select = "cn.ykf.mapper.AccountMapper.getAccountByUid", fetchType = FetchType.LAZY))

? ? })

? ? List<User> listAllUsers();

public interface AccountMapper {

? ? /**

? ? * 根據(jù)用戶id查詢賬戶列表

? ? *

? ? * @param uid

? ? * @return

? ? */

? ? @Select("SELECT * FROM account WHERE uid = #{uid}")

? ? List<Account> listAccountsByUid(Integer uid);

}

5.4 二級緩存

<settings>

? ? <!-- 開啟緩存 -->

? ? <setting name="cacheEnabled" value="true"/>

</settings>

@CacheNamespace(blocking = true)

public interface UserMapper {

// .....

}

6 Mybatis分頁

? ? 主要有三種方法實(shí)現(xiàn),最簡單的就是利用原生的sql關(guān)鍵字limit來實(shí)現(xiàn)项炼,還有一種就是利用interceptor來拼接sql担平,實(shí)現(xiàn)和limit一樣的功能示绊,再一個(gè)就是利用PageHelper來實(shí)現(xiàn)。

6.1 自定義返回對象Pager

public class Pager<T> {

????private int page;//分頁起始頁

????private int size;//每頁記錄數(shù)

????private List<T> rows;//返回的記錄集合

????private long total;//總記錄條數(shù)

}

6.2 limit關(guān)鍵字實(shí)現(xiàn)

· UserMapper.java

public List<User> findByPager(Map<String, Object> params);

public long count();

· UserMapper.xml

<select id="findByPager" resultType="com.xxx.mybatis.domain.User">

????select * from xx_user limit #{page},#{size}

</select>

<select id="count" resultType="long">

????select count(1) from xx_user

</select>

· UserService.java

public Pager<User> findByPager(int page,int size){

????Map<String, Object> params = new HashMap<String, Object>();

????params.put("page", (page-1)*size);

????params.put("size", size);

????Pager<User> pager = new Pager<User>();

????List<User> list = userMapper.findByPager(params);

????pager.setRows(list);

????pager.setTotal(userDao.count());

????return pager;

}

6.3 Interceptor plugin實(shí)現(xiàn)

????定義一個(gè)類實(shí)現(xiàn)Interceptor接口暂论,通過攔截器插件的方式實(shí)現(xiàn)分頁面褐。? ??

·?MyPageInterceptor.java

@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class,Integer.class})})

public class MyPageInterceptor implements Interceptor {

????private int page;

????private int size;

????@SuppressWarnings("unused")
????private String dbType;

????@SuppressWarnings("unchecked")
????@Override
????public Object intercept(Invocation invocation) throws Throwable {

????System.out.println("plugin is running...");

????StatementHandler statementHandler = (StatementHandler)invocation.getTarget();

????MetaObject metaObject = SystemMetaObject.forObject(statementHandler);

????while(metaObject.hasGetter("h")){

????????Object object = metaObject.getValue("h");

????????metaObject = SystemMetaObject.forObject(object);

????}

????while(metaObject.hasGetter("target")){

????????Object object = metaObject.getValue("target");

????????metaObject = SystemMetaObject.forObject(object);

????}

????MappedStatement mappedStatement =
????(MappedStatement)metaObject.getValue("delegate.mappedStatement");

????String mapId = mappedStatement.getId();

????if(mapId.matches(".+ByPager$")){

????????ParameterHandler parameterHandler = ????????(ParameterHandler)metaObject.getValue("delegate.parameterHandler");

????????Map<String, Object> params = (Map<String, ????????Object>)parameterHandler.getParameterObject();

????????page = (int)params.get("page");

????????size = (int)params.get("size");

????????String sql = (String) metaObject.getValue("delegate.boundSql.sql");

????????sql += " limit "+(page-1)*size +","+size;

????????metaObject.setValue("delegate.boundSql.sql", sql);

????}

????return invocation.proceed();

}

@Override

public Object plugin(Object target) {

????return Plugin.wrap(target, this);

}

@Override

public void setProperties(Properties properties) {

????String limit = properties.getProperty("limit","10");

????this.page = Integer.parseInt(limit);

????this.dbType = properties.getProperty("dbType", "mysql");

????}

}

· UserService.java

public Pager<User> findByPager(int page,int size){

????Map<String, Object> params = new HashMap<String, Object>();

????params.put("page", page);

????params.put("size", size);

????Pager<User> pager = new Pager<User>();

????List<User> list = userMapper.findByPager(params);

????pager.setRows(list);

????pager.setTotal(userMapper.count());

????return pager;

}

· spring配置中,增加plugin設(shè)置

圖6-1 Interceptor plugin配置

· UserMapper.xml

<select id="findByPager" resultType="com.xxx.mybatis.domain.User">

????select * from xx_user?

</select>

<select id="count" resultType="long">

????select count(1) from xx_user

</select>

6.4 Pagehelper實(shí)現(xiàn)

· maven依賴

<dependency>

????<groupId>com.github.pagehelper</groupId>

????<artifactId>pagehelper</artifactId>

????<version>4.2.1</version>

</dependency>

· spring.xml

<bean id="pageInterceptor" class="com.github.pagehelper.PageHelper">

????<property name="properties">

????????<props>

????????????<prop key="helperDialect">mysql</prop>

????????????<prop key="reasonable">true</prop>

????????????<prop key="supportMethodsArguments">true</prop>

????????????<prop key="params">count=countSql</prop>

????????</props>

????</property>

</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

????<property name="dataSource" ref="dataSource" />

????<property name="mapperLocations"? ????????value="classpath:com/xxx/mybatis/dao/*Mapper.xml"/>

????<property name="plugins" ref="pageInterceptor"></property>

</bean>

· UserService.java

public Pager<User> findByPager(int page,int size){

????Pager<User> pager = new Pager<User>();

????Page<User> res = PageHelper.startPage(page,size);

????userMapper.findAll();

????pager.setRows(res.getResult());

????pager.setTotal(res.getTotal());

????return pager;

}

????其實(shí)PageHelper方法也是第二種使用Interceptor攔截器方式的一種三方實(shí)現(xiàn)取胎,它內(nèi)部幫助我們實(shí)現(xiàn)了Interceptor的功能展哭。所以我們不用自定義MyPageInterceptor這個(gè)類了。實(shí)際上也是在運(yùn)行查詢方法的時(shí)候闻蛀,進(jìn)行攔截匪傍,然后設(shè)置分頁參數(shù)。所以PageHelper.startPage(page,size)這一句需要顯示調(diào)用觉痛,然后再執(zhí)行userMapper.findAll()役衡,在查詢所有用戶信息的時(shí)候,會進(jìn)行一個(gè)分頁參數(shù)設(shè)置薪棒,讓放回的結(jié)果只是分頁的結(jié)果手蝎,而不是全部集合。

淺析pagehelper分頁原理_王大錘的博客-CSDN博客_pagehelper分頁原理

總結(jié):PageHelper首先將前端傳遞的參數(shù)保存到page這個(gè)對象中俐芯,接著將page的副本存放入ThreadLoacl中棵介,這樣可以保證分頁的時(shí)候,參數(shù)互不影響吧史,接著利用了mybatis提供的攔截器邮辽,取得ThreadLocal的值,重新拼裝分頁SQL贸营,完成分頁逆巍。

7 常見問題匯總

7.1 關(guān)于mybatis的一些面試題

(1)#{}是sql占位符,在預(yù)編譯時(shí)為“?”莽使;${}是配置文件中的占位符锐极,直接被替換成字符。

(2)mybatis將<select><insert>等標(biāo)簽芳肌,唯一解釋為一個(gè)MappedStatement對象灵再。

(3)Mybatis 僅可以編寫針對ParameterHandler、ResultSetHandler亿笤、StatementHandler翎迁、Executor這4種接口的插件,Mybaytis使用JDK的動(dòng)態(tài)代理净薛,為需要攔截的接口生成代理對象以實(shí)現(xiàn)接口方法攔截功能汪榔。

7.2 開發(fā)常見問題總結(jié)

7.2.1 數(shù)據(jù)庫和實(shí)體類映射

· 駝峰命名

數(shù)據(jù)庫表列:user_name

實(shí)體類屬性:userName

在springboot的yml配置如下,一定不要放在spring標(biāo)簽下!G凇胀糜!

mybatis:
????configuration:
????????# 開啟駝峰uName自動(dòng)映射到u_name
????????map-underscore-to-camel-case: true

或者在mybatis配置文件中設(shè)置:

<setting name="mapUnderscoreToCamelCase" value="true" />

· 使用別名括堤。參考3.1節(jié)

7.2.2?Parameter array not found. Available parameters are [collection, list]問題

????當(dāng)我們要查詢一些的信息時(shí),可能會采用list集合或者數(shù)組作為參數(shù)傳入方法中。

<select id="findSomeUsers" resultType="user3" parameterType="list">

????select * from user where id in

????<foreach collection="noList" index="index" item="no" open="(" separator="," close=")">

????????#{no}

????</foreach>

</select>

? ? 這時(shí)報(bào)錯(cuò)是因?yàn)榍钤辏瑐鬟f一個(gè) List 實(shí)例或者數(shù)組作為參數(shù)對象傳給 MyBatis,MyBatis 會自動(dòng)將它包裝在一個(gè) Map 中,用名稱在作為鍵两芳。List 實(shí)例將會以“l(fā)ist” 作為鍵,而數(shù)組實(shí)例將會以“array”作為鍵。解決這個(gè)異常的兩種方式是:
????1.在方法參數(shù)前面加上你遍歷的集合的名稱特咆,比如你在foreach的collection中寫的是noList,那么你就在傳入的list參數(shù)前面加上一個(gè)注解@Param(“noList”)录粱。
????2.將foreach的collection中的值改成list即可腻格。

· 在實(shí)際開發(fā)中,上述報(bào)錯(cuò)啥繁,下面代碼為一項(xiàng)目中使用的正確代碼:

@Select({ "<script>", "select * from `product_category` where category_type in ",

"<foreach collection='categoryTpyeList' item='item' index='index' open='(' separator=',' close=')'>",

"#{item}", "</foreach>", "</script>" })

public List<ProductCategoryEntity> findByCategoryTpye(@Param("categoryTpyeList") List<Integer> categoryTpyeList);

collection: 指定要遍歷的集合(三種情況 list菜职,array,map) F烀觥3旰恕!适室!在這種使用注解sql的情況下嫡意,這里請?zhí)顚憁apper方法中集合的名稱

item:將當(dāng)前遍歷出的元素賦值給指定的變量 (相當(dāng)于for循環(huán)中的i)

separator:每個(gè)元素之間的分隔符

index:索引。遍歷list的時(shí)候是index就是索引捣辆,item就是當(dāng)前值

#{變量名}就能取出變量的值也就是當(dāng)前遍歷出的元素

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蔬螟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子汽畴,更是在濱河造成了極大的恐慌旧巾,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件整袁,死亡現(xiàn)場離奇詭異菠齿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)坐昙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門绳匀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事疾棵「旮郑” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵是尔,是天一觀的道長殉了。 經(jīng)常有香客問我,道長拟枚,這世上最難降的妖魔是什么薪铜? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮恩溅,結(jié)果婚禮上隔箍,老公的妹妹穿的比我還像新娘。我一直安慰自己脚乡,他們只是感情好蜒滩,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奶稠,像睡著了一般俯艰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锌订,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天竹握,我揣著相機(jī)與錄音,去河邊找鬼瀑志。 笑死涩搓,一個(gè)胖子當(dāng)著我的面吹牛污秆,可吹牛的內(nèi)容都是我干的劈猪。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼良拼,長吁一口氣:“原來是場噩夢啊……” “哼战得!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起庸推,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤常侦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后贬媒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聋亡,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年际乘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了坡倔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,912評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖罪塔,靈堂內(nèi)的尸體忽然破棺而出投蝉,到底是詐尸還是另有隱情,我是刑警寧澤征堪,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布瘩缆,位于F島的核電站,受9級特大地震影響佃蚜,放射性物質(zhì)發(fā)生泄漏庸娱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一谐算、第九天 我趴在偏房一處隱蔽的房頂上張望涌韩。 院中可真熱鬧,春花似錦氯夷、人聲如沸臣樱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雇毫。三九已至,卻和暖如春踩蔚,著一層夾襖步出監(jiān)牢的瞬間棚放,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工馅闽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飘蚯,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓福也,卻偏偏與公主長得像局骤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子暴凑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評論 2 361