目錄
????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實(shí)現(xiàn)
????不同的數(shù)據(jù)庫提供不同的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();
? ? ? ? }
? ? }
}
· 構(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 源碼分析
? ? 在通過代理模式生成代理對象后举塔,調(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.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í)乾翔,就會清空一級緩存爱葵。
· 第一次發(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 的弛秋。
· 當(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 單表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è)置
· 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)前遍歷出的元素