# Mybatis-9.28 環境: - JDK:1.8 - Mysql:8.032 - maven:3.9.2 - IDEA 回顧: - JDBC - Mysql - JavaSE - Maven - Junit ## 01 簡介 ### 1.1 什麼是MyBatis ![](https://im ...
Mybatis-9.28
環境:
- JDK:1.8
- Mysql:8.032
- maven:3.9.2
- IDEA
回顧:
- JDBC
- Mysql
- JavaSE
- Maven
- Junit
01 簡介
1.1 什麼是MyBatis
- MyBatis 是一款優秀的持久層框架。
- 它支持自定義 SQL、存儲過程以及高級映射。
- MyBatis 免除了幾乎所有的 JDBC 代碼以及設置參數和獲取結果集的工作。
- MyBatis 可以通過簡單的 XML 或註解來配置和映射原始類型、介面和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)為資料庫中的記錄。
如何獲得Mybatis?
- maven倉庫:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
- Github:https://github.com/mybatis/mybatis-3/releases
- 中文文檔:https://mybatis.org/mybatis-3/zh/index.html
1.2 持久化
數據持久化
-
持久化就是將程式的數據在持久狀態和瞬時狀態轉化的過程
-
記憶體:斷電即失
-
資料庫(jdbc),io文件持久化。
為什麼需要持久化?
-
有一些對象,不能讓他丟掉。
-
記憶體太貴了
1.3 持久層
Dao層 Service層 Controller層。。。
- 幫助程式員將數據存入資料庫中
- 方便
- 傳統的JDBC太複雜了。簡化、框架、自動化。
- 不用Mybatis也可以。但更容易上手。
- 優點:
- 簡單易學
- 靈活
- sql和代碼的分離,提高了可維護性。
- 提供映射標簽,支持對象與資料庫的ORM欄位關係映射。
- 提供對象關係映射標簽,支持對象關係組建維護。
- 提供xml標簽,支持編寫動態sql。
02 第一個Mybatis程式
思路:搭建環境-->導入Mybatis-->編寫代碼-->測試
2.1 搭建環境
- 搭建資料庫
-
新建項目
- 新建一個普通maven項目
- 刪除src目錄,使該項目成為父工程
- 導入maven依賴
<dependencies> <!--資料庫連接--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.31</version> </dependency> <!--mybatis--> <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> <!--junit測試包--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> </dependency> </dependencies>
2.2 創建一個模版
-
編寫mybatis核心配置文件
- 記住修改driver、url、username和password
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <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="你自己配置的xml文件路徑"/> </mappers> </configuration>
-
編寫mybatis工具類
/*提升對象作用域*/ private static SqlSessionFactory sqlSessionFactory; static{ try { //使用mybatis第一步,獲取sqlSessionFactory對象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { throw new RuntimeException(e); } } //既然有了SqlSessionFactory,顧名思義,我們就可以從中獲得SqlSession的實例了。 //SqlSession完全包含了面向資料庫執行SQL命令所需的全部方法。 public static SqlSession getsqlSession(){ return sqlSessionFactory.openSession(); }
2.3 編寫代碼
-
實體類(註意和資料庫表保持一致)
private int id; private String name; private String pwd; private Date createTime; private Date modifyTime; public User() { } public User(int id, String name, String pwd, Date createTime, Date modifyTime) { this.id = id; this.name = name; this.pwd = pwd; this.createTime = createTime; this.modifyTime = modifyTime; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getModifyTime() { return modifyTime; } public void setModifyTime(Date modifyTime) { this.modifyTime = modifyTime; } @Override public String toString() { return "User{" + "id=" + id + ", name='" + name + '\'' + ", pwd='" + pwd + '\'' + ", createTime=" + createTime + ", modifyTime=" + modifyTime + '}'; }
-
Dao介面
List<User> getUserList();
-
Dao介面實現類由原來的UserDaoImpl轉變為一個Mapper配置文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--綁定一個對應的Dao/Mappper介面--> <mapper namespace="com.wu.dao.UserDao"> <!--select查詢語句--> <select id="getUserList" resultType="com.wu.pojo.User"> select * from mybatis.user </select> </mapper>
2.4 測試
註意點!!!!
首先需要註意一個需要修改的地方,即2.2創建模版中有一處需要配置自己xml地址的地方,在正式運行前需要更改文件路徑。路徑格式為:
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
在test中編寫測試類方法:
public void test(){
//第一步:獲得SqlSession對象
SqlSession sqlSession = MybatisUtils.getsqlSession();
//執行SQL
UserDao userDao = sqlSession.getMapper(UserDao.class);
List<User> userList = userDao.getUserList();
for (User user:userList){
System.out.println(user);
}
//關閉SqlSession
sqlSession.close();
}
常見易錯問題:
1、配置文件沒有註冊
2、綁定介面有問題
3、方法名不對
4、返回類型不對
5、Maven導出資源問題
補充:作用域和生命周期
-
SqlSessionFactoryBuilder
- 這個類可以被實例化、使用和丟棄,一旦創建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變數)。 你可以重用 SqlSessionFactoryBuilder 來創建多個 SqlSessionFactory 實例,但最好還是不要一直保留著它,以保證所有的 XML 解析資源可以被釋放給更重要的事情。
-
SqlSessionFactory
- SqlSessionFactory一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例。 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重覆創建多次,多次重建 SqlSessionFactory 被視為一種代碼“壞習慣”。因此 SqlSessionFactory 的最佳作用域是應用作用域。 有很多方法可以做到,最簡單的就是使用單例模式或者靜態單例模式。
-
SqlSession
- 每個線程都應該有它自己的SqlSession實例。SqlSession的的實例不是線程安全的,因此是不能被共用的,所以它的最佳的作用域是請求或方法作用域。 絕對不能將 SqlSession 實例的引用放在一個類的靜態域,甚至一個類的實例變數也不行。 也絕不能將 SqlSession 實例的引用放在任何類型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你現在正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求相似的作用域中。 換句話說,每次收到 HTTP 請求,就可以打開一個SqlSession,返回一個響應後,就關閉它。 這個關閉操作很重要,為了確保每次都能執行關閉操作,你應該把這個關閉操作放到 finally 塊中。
03 CRUD
3.1 Select
- 選擇,查詢語句
- id:就是對應的namespace中的方法名;
- resultType:Sql語句執行的返回值
- parameterType:傳入的參數類型
註意:增刪改需要提交事務!!
3.2-3.4 Insert Update Delete(暫缺,後續補齊)
3.5 分析錯誤
- 標簽註意不要配錯
- resource綁定mapper,需要使用路徑(需要註意resource地址採取“/”和mapper中關於parameterType的配置採取“.”)
- 程式的配置文件必須符合規範
- NullPointerException,沒有註冊到資源!
- 輸出的xml文件中存在中文亂碼問題!
- maven資源節點導出問題!
3.6 Map和模糊查詢
3.6.1 Map
假設,實體類或者資料庫中的表,欄位或者參數過多,應當考慮使用Map!(主要在修改update類型中使用)
int addUserByMap(Map<String, Object> map);
<insert id="addUserByMap" parameterType="map">
insert into mybatis.user values(#{id},#{name},#{pwd},#{createTime},#{modifyTime});
</insert>
@Test
public void test6(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper map = sqlSession.getMapper(UserMapper.class);
Map<String, Object> user = new HashMap<String,Object>();
user.put("id",4);
user.put("name","liutianbo");
user.put("pwd","666666");
user.put("createTime",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime()));
int i = map.addUserByMap(user);
if(i>0){
sqlSession.commit();
System.out.println("添加成功,影響"+i+"行");
}
sqlSession.close();
}
Map傳遞參數,直接在sql中取出key即可!【parameterType="map"】
對象傳遞參數,直接在sql中取對象的屬性即可!【parameterType="Object"】
只有一個基本類型參數的情況下,可以直接在sql中取到(即無須定義傳入參數類型)
多個參數用Map,或者註解!
3.6.2 模糊查詢
模糊查詢怎麼寫?
1、Java代碼執行的時候,傳遞通配符%%
List<User> userList = mapper.getLikeUser("%張%");
2、在sql拼接中使用通配符!
select * from mybatis.user where name like "%"#{value}"%"
- 但需要註意的是,在sql拼接種可能會帶來sql註入問題,可能會給系統帶來一些安全性問題
04 配置解析
4.1 核心配置文件
- mybatis-config.xml
- MyBatis的配置文件包含了會深深影響Mybatis新聞的設置和屬性信息
4.2 環境配置(Enviroments)
-
MyBatis 可以配置成適應多種環境
-
不過要記住:儘管可以配置多個環境,但每個 SqlSessionFactory 實例只能選擇一種環境。
-
Mybatis的預設事務管理器就是JDBC,連接池:POOLED
4.3 屬性(properties)
我們可以通過properties屬性來實現引用配置文件。
這些屬性都是可外部配置且可動態替換的,既可以在典型的Java屬性文件中配置,亦可通過properties元素的子元素來傳遞。【db.properties】
編寫一個配置文件
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username = root
password = 12345
在核心配置文件中引入,但需要註意
<!--引入外部配置文件-->
<properties resource="db.properties"></properties>
- 可以直接引入外部文件
- 可以在xml中增加一些屬性配置
- 如果外部文件和xml中有對同一個欄位進行配置,優先使用外部配置文件的!
4.4 類型別名(typeAliases)
-
類型別名可為 Java 類型設置一個縮寫名字。
-
它僅用於 XML 配置,意在降低冗餘的全限定類名書寫。
<typeAliases> <typeAlias alias="User" type="com.wu.pojo.User"/> </typeAliases>
-
也可以指定一個包名,Mybatis會在包名下麵搜索需要的Java Bean,然後使用 Bean 的首字母小寫的非限定類名來作為它的別名,或者直接使用註解作為其別名
<typeAliases> <!--<typeAlias alias="User" type="com.wu.pojo.User"/>--> <package name="com.wu.pojo"/> </typeAliases>
在實體類比較少的時候,使用第一種方式。
如果實體類十分多,建議使用第二種方式。
第一種可以自定義別名;第二種則不行,除非使用@Alias註解,例如:
@Alias("user") public class User{ }
4.5 設置(Settings)
這是 MyBatis 中極為重要的調整設置,它們會改變 MyBatis 的運行時行為。
比較重要的設置:
4.6 其他設置
- typeHandlers(類型處理器)
- objectFactory(對象工廠)
- plugins插件
- mybatis-generator-core
- mybatis-plus
- 通用mapper
4.7 映射器(mappers)
MapperRegistry:註冊綁定我們的Mapper文件;
方式一:使用相對於類路徑的資源引用
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中註冊!-->
<mappers>
<!-- <mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<mapper resource="com/wu/dao/UserMapper.xml"/>
</mappers>
方式二:使用完全限定資源定位符(URL)
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中註冊!-->
<mappers>
<!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--<mapper resource="com/wu/dao/UserMapper.xml"/>-->
<mapper class="com.wu.dao.UserMapper"></mapper>
</mappers>
方式三:將包內的映射器介面全部註冊為映射器
<!--每一個Mapper.xml都需要在Mybatis核心配置文件中註冊!-->
<mappers>
<!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--<mapper resource="com/wu/dao/UserMapper.xml"/>-->
<!--<mapper class="com.wu.dao.UserMapper"></mapper>-->
<package name="com.wu.dao"/>
</mappers>
但需要註意的是,方式二和方式三有兩個需要註意的地方:
- Interface類文件必須和成對的Mapper配置文件同名
- Interface文件和它配對的Mapper文件必須在同一個包下
4.8 生命周期和作用域
生命周期和作用域,是指關重要的,因為錯誤的使用會導致非常嚴重的併發問題。
SqlSessionFactoryBuilder:
- 一旦創建了SqlSessionFactoryBuilder,就不再需要;
- 因此作為局部變數
SqlSessionFactory:
- 可以想象為資料庫連接池
- SqlSessionFactory一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄她或重新創建另一個實例。
05 ResultMap結果集映射
5.1 問題
前面所述實體類的各個屬性名稱和資料庫欄位是一一對應的,因此輸出的結果都是正常的。
如果對應不上會發生什麼?比如將pwd改成password?
結果很明顯,這個欄位的信息讀不出來。
回看一下mapper中編寫的sql語句
select * from mybatis.user
實際上星號部分省略了
select id,name,pwd,createtime,modifytime from mybatis.user
因此,這個欄位名取出後進行映射時自然找不到password名的欄位。
5.2 解決方法一(修改sql語句)
解決這個問題有兩種常見方法,第一種就是修改語句,給資料庫欄位pwd取別名。
select id,name,pwd as password,createtime,modifytime from mybatis.user
運行後發現,確實可以取出來:
但這個方法並不好,原因在於如果sql語句較多,就需要每一個都配置相關的別名,會極大增加工作量。
5.3 解決方法二(使用ResultMap映射集)
在Mapper文件中配置resultMap(註意各個部分的順序問題!)
<resultMap id="UserMap" type="User">
<result column="id" property="id"/>
<result column="name" property="name"/>
<result column="pwd" property="password"/>
<result column="createtime" property="createTime"/>
<result column="modifytime" property="modifyTime"/>
</resultMap>
column指的是資料庫的欄位,property指的是對象中的屬性名
註意,除了配置resultmap外,還有一處需要修改:
紅框圈出部分原來是resultType,再使用結果集映射後需要換為resultMap
- resultmap元素是mybatis中最重要最強大的元素
- resultmap的設計思想是,對於簡單的語句根本不需要配置顯式的結果映射,而對於複雜一點的雨具只需要描述它們之間的關係就行了
- ResultMap的最優秀之處在於,雖然你已經對他相當瞭解了,但是根本就不需要現實的用到它們。
06 日誌
6.1 日誌工廠
如果一個資料庫操作出現了異常,需要人為排錯,日誌就是非常好的幫手。
曾經:debug、sout
現在:日誌工廠
- SLF4J
- LOG4J(3.5.9 起廢棄) (重點)
- LOG4J2 (重點)
- JDK_LOGGING
- COMMONS_LOGGING
- STDOUT_LOGGING (重點)
- NO_LOGGING
在mybatis中具體使用哪一種日誌,在config.xml文件中配置
註意:還記得前面說的配置順序問題嗎,需要註意!
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
- 可以看到,黃色部分是mybatis打開JDBC連接,以及創建資料庫連接的過程,同時將自動提交設置為否。
- 紅框部分包含所執行的sql語句以及在資料庫中的查詢結果
- 紅框和藍框間是查詢結果
- 藍框是執行完sql後一些設置,包括重新開啟自動提交以及斷開JDBC連接
6.2 LOG4J
什麼是Log4j?
- Log4j是Apache的一個開源項目,通過使用Log4j,我們可以控制日誌信息輸送的目的地是控制台、文件、GUI組件等等。
- 我們也可以控制每一條日誌的輸出格式;
- 通過定義每一條日誌信息的級別,可以使我們更加細緻的控制日誌的生成過程。
- 通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼
如何快速開始?
1、先導入log4j的jar包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、配置log4j.properties
#將等級為DEBUG的日誌信息輸出到console和file兩個目的地,console和file的定義在下麵代碼
log4j.rootLogger = DEBUG,console,file
#控制台輸出的相關設置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold = DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern = [%c]-%m%n
#文件輸出的相關設置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File = ./log/wu.log
log4j.appender.file.MaxFileSize = 10mb
log4j.appender.file.Threshold = DEBUG
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern = [%p][%d{yyyy-MM-dd}][%c]%m%n
#日誌輸出級別
log4j.logger.org.mybatis = DEBUG
log4j.logger.java.sql = DEBUG
log4j.logger.java.sql.Statement = DEBUG
log4j.logger.java.sql.ResultSet = DEBUG
log4j.logger.java.sql.PreparedStatement = DEBUG
3、配置log4j為日誌的實現
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
如何使用日誌?
-
在要使用Log4j的類中,導入包import org.apache.log4j.Logger
-
創建日誌對象,參數為當前類的class
static Logger logger = Logger.getLogger(XXX.class);
-
日誌級別
logger.info("info:進入了log4j"); logger.error("error:進入了log4j"); logger.debug("debug:進入了log4j"); logger.warn("warn:進入了log4j");
07 分頁
7.1 使用Limit分頁
思考:為什麼要分頁?
- 減少數據的處理量,提高速度
使用Limit分頁
SELECT * FROM `user` limit startIndex,pageSize;
使用Mybatis實現分頁 ,核心SQL
1、介面
List<User> getUserByLimit(Map<String,Integer> map);
2、Mapper.xml
<select id="getUserByLimit" resultMap="UserMap" parameterType="map">
select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
3、編寫測試方法
@Test
public void testPage(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",0);
map.put("pageSize",2);
List<User> userByLimit = mapper.getUserByLimit(map);
for (User user : userByLimit) {
System.out.println(user);
}
sqlSession.close();
}
7.2 使用RowBounds分頁
不再使用sql實現分頁,使用一個對象實現分類。
新建一個RowBounds對象,然後傳入兩個參數,分別是偏差(起始位=偏差+1)和條數限制
RowBounds bounds = new RowBounds(1,2);
介面:
List<User> getUserByRowBounds();
mapper.xml:
<select id="getUserByRowBounds" resultMap="UserMap">
select * from mybatis.user
</select>
測試:
@Test
public void testRowBounds(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
/*新建bounds對象*/
RowBounds bounds = new RowBounds(1,2);
/*通過java代碼層面實現分頁*/
List<User> userlist = sqlSession.selectList("com.wu.mapper.UserMapper.getUserByRowBounds",null,bounds);
for (User user : userlist) {
System.out.println(user);
}
sqlSession.close();
}
7.3 分頁插件
08 使用註解開發
8.1 面向介面編程
- 學習Java,相比於c語言,一個比較大的區別在於編程思路上。Java是一門面向對象的編程語言,同時隨著對架構的學習,也知道了包括介面等其他知識。在真正開發中,相比於面向對象,大多數時候人們會選擇面向介面編程
- 根本原因:解耦,可拓展,提高復用,分層開發中,上層不用管具體的實現,開發人員遵守共同的標準,使得開發變得容易,規範性更好
- 在一個面向對象的系統中,系統的各種功能時由許許多多的不同對象協作完成的。在這種情況下,各個對象內部是如何實現自己的,對於系統設計人員來講就並不重要。
- 但與此同時,各個對象之間的協作關係則成為系統設計的關鍵。小到不同類之間的通信,大到各模塊之間的交互,在系統設計之初便是要著重考慮的,這也是系統設計的主要工作內容。面向介面編程就是指按照這種思想來編程。
關於介面的理解
- 介面從更深層次的理解,應是定義(規範,約束)與實現(名實分離的原則)的分離
- 介面的本身反映了系統設計人員對系統的抽象理解
- 介面應有2類:
- 第一類是對一個個體的抽象,它可對應為一個抽象體(abstract class)
- 第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface)
- 一個個體有可能有多個抽象面。抽象體與抽象面是有區別的。
三個面向的區別
- 面相對象是指,我們考慮問題時,以對象為單位,考慮它的屬性和方法
- 面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現。
- 介面設計與非介面設計是針對復用技術而言的,與面向對象(過程)不是一個問題。更多的體現就是對系統整體的架構的思考。
8.2 使用註解開發
也就是說,不需要配置xml實現介面中的sql,直接在介面文件中使用註解編寫sql。
介面:
@Select("select * from user")
List<User> getUserList();
修改映射文件配置:
<mappers>
<!--<mapper resource="org/mybatis/example/BlogMapper.xml"/>-->
<!--<mapper resource="com/wu/mapper/UserMapper.xml"/>-->
<!--<mapper class="com.wu.dao.UserMapper"></mapper>-->
<!--<package name="com.wu.dao"/>-->
<mapper class="wu.mapper.UserMapper"></mapper>
</mappers>
編寫測試方法:
public void test(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
但是可以發現,由於先前更改了實體類中的pwd為password,因此在採取註解編程時,就無法採取在xml中配置結果集映射的方式。使用註解來映射簡單語句會使代碼顯得更加簡潔,但對於稍微複雜一點的語句,Java 註解不僅力不從心,還會讓本就複雜的 SQL 語句更加混亂不堪。 因此,如果需要做一些很複雜的操作,最好用 XML 來映射語句。
本質:反射機制!
底層:動態代理!
8.3 Mybatis的詳細執行流程(暫缺,後續補齊)
8.4 CRUD
首先,為了方便起見,在創建sqlsession時便設置自動提交事務
public static SqlSession getsqlSession(){
return sqlSessionFactory.openSession(true);
}
編寫介面,用註解實現增刪改查
@Select("select * from user")
List<User> getUserList();
/*@Param 如果方法存在多個參數,則所有的參數前面必須加上@Param*/
@Select("select * from user where id = #{id}")
User getUserById(@Param("id") int id /*,@Param("name") String name*/);
@Insert("insert into user(id,name,pwd,createtime,modifytime) values(#{id},#{name},#{password},#{createTime},#{modifyTime})")
int addUser(User user);
@Update("update `user` set name = #{name} , pwd = #{password} , createtime = #{createTime} , modifytime = #{modifyTime} where id = #{id}")
int updateUser(User user);
@Delete("delete from `user` where id = #{id}")
int deleteUser(@Param("id") int id);
編寫測試類,驗證相關功能
[註意,每新建一個mapper,都要綁定相關的介面文件]
對於多個mapper.xml文件,可以在前面採取*方式進行通配
<mapper resource="com/wu/mapper/*Mapper.xml"/>
@Test
public void test(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
/* List<User> userList = mapper.getUserList();
for (User user : userList) {
System.out.println(user);
}*/
/* User user = mapper.getUserById(1);*/
User user = new User();
user.setId(7);
user.setName("張恩普");
user.setPassword("123456");
user.setCreateTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
int i = mapper.addUser(user);
if(i>0) {
System.out.println("添加成功!影響"+i+"行");
}
sqlSession.close();
}
@Test
public void test2(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user1 = mapper.getUserById(4);
User user2 = new User(4,"劉天博","987654321", user1.getCreateTime(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date().getTime()));
int i = mapper.updateUser(user2);
if (i>0){
System.out.println("修改完成,影響"+i+"行");
}
sqlSession.close();
}
@Test
public void test3(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUser(5);
if(i>0){
System.out.println("修改完成,影響"+i+"行");
}
sqlSession.close();
}
8.5 Param註解
關於@Param()註解
- 基本類型的參數或者String類型,需要加上
- 引用類型不需要加
- 如果只有一個基本類型的話,可以忽略,但是建議大家都加上
- 我們在SQL中引用的就是我們這裡的@Param()中設定的屬性名!
09 Lombok
9.1 什麼是lombok
- 官網英文介紹:
Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
-
翻譯一下:
Lombok項目是一個java庫,它可以自動插入你的編輯器和構造工具,從而實現簡化代碼和提高java程式的趣味。使用lombk將讓你不需要再另寫一個getter或setter方法。使用一個註釋,你的類就將有一個完整功能的構造器,同時還可以實現包括自動配置你的日誌變數等等其他更多功能。
-
各種註釋欄位
@Setter 註解在類或欄位,註解在類時為所有欄位生成setter方法,註解在欄位上時只為該欄位生成setter方法。
@Getter 使用方法同上,區別在於生成的是getter方法。
@ToString 註解在類,添加toString方法。
@EqualsAndHashCode 註解在類,生成hashCode和equals方法。
@NoArgsConstructor 註解在類,生成無參的構造方法。
@RequiredArgsConstructor 註解在類,為類中需要特殊處理的欄位生成構造方法,比如final和被@NonNull註解的欄位。
@AllArgsConstructor 註解在類,生成包含類中所有欄位的構造方法。@Data 註解在類,生成setter/getter、equals、canEqual、hashCode、toString、無參構造方法,如為final屬性,則不會為該屬性生成setter方法。
@Slf4j 註解在類,生成log變數,嚴格意義來說是常量。private static final Logger log = LoggerFactory.getLogger(UserController.class); -
使用步驟:
-
在IDEA中安裝Lombok插件
-
在項目中導入lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency>
-
在實體類中使用lombok
@Data @AllArgsConstructor public class User { private Integer id; private String name; private String password; private String createTime; private String modifyTime; }
-
在pojo的user類添加@Data後:
-
10 複雜查詢環境(多對一處理和一對多處理)
10.1 測試環境搭建
-
資料庫建表
CREATE TABLE `teacher` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO teacher(`id`, `name`) VALUES (1, '吳老師'); INSERT INTO teacher(`id`, `name`) VALUES (2, '袁老師'); CREATE TABLE `student` ( `id` INT(10) NOT NULL, `name` VARCHAR(30) DEFAULT NULL, `tid` INT(10) DEFAULT NULL, PRIMARY KEY (`id`), KEY `fktid` (`tid`), CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小紅', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小張', '1'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '2'); INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '2');
-
導入lombok(具體操作見第9章)
-
新建實體類Teacher和Student
@Data @NoArgsConstructor @AllArgsConstructor public class Teacher { private int id; private String name; }
@Data @AllArgsConstructor @NoArgsConstructor public class Student { private int id; private String name; /*學生需要關聯一個老師*/ private Teacher teacher; }
-
在dao文件下建立Mapper介面以及Mapper.xml文件
-
在核心配置文件中綁定註冊Mapper介面或者文件(我這裡給出兩種,要麼用包定義,要麼用下麵那兩個去調Mapper.xml)
- 測試一下是否能夠成功查詢
10.2 多對一處理
-
首先在資料庫中嘗試實現查詢學生和關聯老師姓名:
select s.id,s.name,t.name from student s ,teacher t where s.tid=t.id;
-
但是,這樣的sql放到mybatis中卻並不好使。
<select id="getStudent" resultType="student"> select s.id,s.name,t.name from student s ,teacher t where s.tid=t.id; </select>
很簡單,因為在資料庫中查不到teacher屬性的數據。因此查出來為空很好理解
- 解決辦法:
- 按照查詢嵌套處理
- 按照結果嵌套處理
10.2.1 按照查詢嵌套處理
<!--
思路:
查詢所有的學生信息
根據查詢出來的學生的tid,尋找對應的老師
方式一:按照查詢嵌套處理
-->
<select id="getStudent1" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--複雜的屬性,我們需要單獨處理 對象association 集合collection-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{tid}
</select>
10.2.2 按照結果嵌套處理
<!--
思路二:
直接在查詢的結果中,將student中teacher的名字屬性映射到teacher表的name上不就行了?
-->
<select id="getStudent2" resultMap="StudentTeacher2">
select s.id sid,s.name sname,t.id tid ,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 集合collection-->
<association property="teacher" javaType="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
回顧Mysql多對一查詢方式:
- 子查詢
- 聯表查詢
10.3 一對多處理
-
環境搭建和10.1類似,但是需要適當修改實體類
public class Student { private int id; private String name; private int tid; }
public class Teacher { private int id; private String name; /*一個老師關聯多個學生*/ private List<Student> studentList; }
10.3.1 按照結果嵌套處理
首先梳理一下思路,按照結果嵌套處理,和10.2.2一樣,就是只查詢一次,然後在查詢過程中配置好映射關係即可。
<!--按結果嵌套查詢-->
<resultMap id="TeacherStudent1" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--針對複雜的屬性,我們需要單獨處理 對象:association 集合:collection
javaType="" 指定屬性的類型
集合中的泛型信息,使用ofType獲取
-->
<collection property="student" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
Teacher getTeacher1(@Param("tid") int id);
10.3.2 按照查詢嵌套處理
相比於按照結果嵌套查詢處理,按照查詢嵌套處理會出現多次查詢,然後對映射關係的配置相比之下略微簡單。但註意此時在collection中需要額外配置許多其它參數,而且實際上在對映射關係的理解上更加複雜。
<!--按查詢嵌套處理-->
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<result property="id" column="id"/>
<result property="name" column="name"/>
<collection property="student" column = "id" javaType="ArrayList" ofType="Student" select="getStudent"/>
</resultMap>
<select id="getStudent" resultType="Student">
select * from student where tid = #{id}
</select>
Teacher getTeacher2(@Param("tid") int id);
10.4 總結
1、關聯-association【多對一】
2、集合-collection【一對多】
3、javaType & ofType
-
- JavaType 用來指定實體類中屬性的類型
-
- ofType 用來指定映射到List或者集合中的pojo類型,是在使用泛型時對所傳內容的類型進行約束
4、註意點
- 保證SQL可讀性,儘量保證通俗易懂
- 註意一對多和多對一中,屬性名和欄位的問題
- 如果問題不好排查錯誤,可以使用日誌,建議使用Log4j
面試高頻:
- Mysql引擎
- InnoDB底層原理
- 索引
- 索引優化!
11 動態SQL
什麼是動態SQL:動態SQL就是根據不用的條件生成不同的SQL語句。
動態 SQL 是 MyBatis 的強大特性之一。如果你使用過 JDBC 或其它類似的框架,你應該能理解根據不同條件拼接 SQL 語句有多痛苦,例如拼接時要確保不能忘記添加必要的空格,還要註意去掉列表最後一個列名的逗號。利用動態 SQL,可以徹底擺脫這種痛苦。
使用動態 SQL 並非一件易事,但藉助可用於任何 SQL 映射語句中的強大的動態 SQL 語言,MyBatis 顯著地提升了這一特性的易用性。 -Mybatis官網
if
choose(when,otherwise)
trim(where,set)
foreach
新建一個工程用於接下來學習(導入jar包和修改配置文件略):
- 新建一張表
CREATE TABLE `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` varchar(100) NOT NULL COMMENT '博客標題',
`author` varchar(30) NOT NULL COMMENT '博客作者',
`create_time` datetime NOT NULL COMMENT '創建時間',
`views` int(30) NOT NULL COMMENT '瀏覽量'
)ENGINE=INNODB DEFAULT CHARSET=utf8;
- 編寫實體類
@Data
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
}
- 啟用駝峰命名映射
<settings>
<!--是否開啟駝峰命名自動映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
- 編寫測試方法寫入數據
//插入數據
int addBook(Blog blog);
<insert id="addBook" parameterType="blog">
insert into mybatis.blog (id,title,author,create_time,views)
Values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
@Test
public void addBlogTest(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IdUtils.getId());
blog.setTitle("Mybatis");
blog.setAuthor("wutong");
blog.setCreateTime(new Date());
blog.setViews(10);
int i = mapper.addBook(blog);
blog.setId(IdUtils.getId());
blog.setTitle("Java");
blog.setAuthor("wutong");
blog.setViews(100);
i+=mapper.addBook(blog);
blog.setId(IdUtils.getId());
blog.setTitle("Spring");
blog.setAuthor("tianbo");
blog.setViews(1000);
i+= mapper.addBook(blog);
blog.setId(IdUtils.getId());
blog.setTitle("微服務");
blog.setAuthor("jibo");
blog.setViews(9999);
i+=mapper.addBook(blog);
System.out.println("執行完成!影響"+i+"行");
sqlSession.close();
}
11.1 IF使用
利用傳入的map內的值來判斷是否增加如下的條件語句。
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from mybatis.blog where 1=1
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</select>
思考:為什麼要加入"1=1"這種恆等式?
這樣做的目的是在sql進行拼接時,不會出現由於前面沒有其他條件直接出現"where and"的情況,致使sql語句報錯。使用1=1將使得其他額外查詢條件採取統一的 "and+條件"的格式。當然也有別的解決方法,如使用11.3中的where語句。
11.2 Choose(when,otherwise)
Choose配合when使用更加近似於Java中的switch case語句,看下麵這個例子:
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<choose>
<when test="title != null">
and title = #{title}
</when>
<when test="author != null">
and author =#{author}
</when>
<when test="views!=null">
and views >= #{views}
</when>
<otherwise>
1=1
</otherwise>
</choose>
</where>
</select>
註意,當傳入的條件從上到下進入一個when後,就不會再走更下麵的的when,即使條件同樣滿足。(類似java的break)
11.3 Trim(where,set)
11.3.1 where
<select id="queryBlogIf2" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="title!=null">
title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</where>
</select>
where 元素只會在子元素返回任何內容的情況下才插入 “WHERE” 子句。而且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
大白話就是,如果只有一個查詢條件,會自動去掉條件前面的and。如果子句前面有and/or,而且前面無其他條件時,也會去掉。
如果寫成下麵這種的,也對。但註意,or和and只能多不能少。如果少了會出現報錯!
<select id="queryBlogIf2" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<if test="title!=null">
and title = #{title}
</if>
<if test="author!=null">
and author = #{author}
</if>
</where>
</select>
11.3.2 set
<update id="updateBlog" parameterType="blog">
update mybatis.blog
<set>
<if test="title!=null">
title = #{title},
</if>
<if test="views!=0">
views = #{views},
</if>
</set>
where author = #{author}
</update>
@Test
public void test4(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
Blog blog = new Blog();
/*blog.setViews(10001);*/
blog.setTitle("學習神經網路");
blog.setAuthor("jibo");
int i = mapper.updateBlog(blog);
if(i>0){
System.out.println("執行完成!影響"+i+"行");
}
sqlSession.close();
}
所謂的動態sql,本質還是SQL語句,只是我們可以在SQL層面,去執行一個邏輯代碼。
if
where ,set,choose,when
11.4 SQL片段(可插拔腳本)和Foreach
11.4.1 SQL片段
有的時候,我們可能會講一些功能的部分抽取出來,方便復用!
-
使用SQL標簽抽取公共的部分
<sql id="testSqlPart"> <if test="title!=null"> and title = #{title} </if> <if test="author!=null"> and author = #{author} </if> </sql>
-
在需要使用的地方使用include標簽引用即可
<select id="queryBlogIf" parameterType="map" resultType="blog"> select * from mybatis.blog <where> <include refid="testSqlPart"></include> </where> </select>
- 註意事項:
- 最好基於單表來定義SQL片段!
- SQL片段內不要存在where標簽,where放在主體內
11.4.2 Foreach
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from mybatis.blog
<where>
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id = #{id}
</foreach>
</where>
</select>
@Test
public void test6(){
SqlSession sqlSession = MybatisUtils.getsqlSession();
BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
HashMap map = new HashMap();
ArrayList< String> ids = new ArrayList<String>();
ids.add("2f49a219363a49fd9714697b342db31a");
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
for (Blog blog : blogs) {
System.out.println(blog);
}
sqlSession.close();
}
動態SQL就是在拼接SQL語句,我們主要保證SQL的 正確性,按照SQL的格式,去排列組合即可
建議:
- 先在Mysql中寫出完整的SQL再對應修改成為我們的動態SQL實現通用即可!
12 緩存
12.1簡介
1、什麼是緩存(Cache)
- 存在記憶體中的臨時數據
- 將用戶經常查詢的數據放在緩存(記憶體)中,用戶查詢數據就不用從磁碟上(關係型資料庫數據文件)查詢,從緩存中查詢,從而提高查詢效率,解決高併發系統的性能問題。
2、為什麼使用緩存?
- 減少和資料庫的交互次數,減少系統開銷,提高系統效率。
3、什麼樣的數據能使用緩存?
- 經常查詢並且不經常改變的數據。
12.2 Mybatis緩存
- Mybatis包含一個非常強大的查詢緩存特性,它可以非常方便的定製和配置緩存。緩存可以極大的提升查詢效率。
- Mybatis系統中預設定義了兩級緩存:一級緩存和二級緩存
- 預設情況下,只有一級緩存開啟。(sqlsession級別的緩存,也稱為本地緩存。一旦關閉sqlsession,則緩存內容丟失。)
- 二級緩存需要手動開啟和配置,其是基於namespace級別的緩存。
- 為了提高擴展性,Mybatis定義了緩存介面Cache。我們可以通過實現Cache介面來自定義二級緩存。
12.3 一級緩存
- 一級緩存也叫本地緩存:
- 與資料庫同一次會話期間查詢到的數據會放在本地緩存中。
- 以後如果需要獲取想用的數據,則直接從緩存中拿,沒必要再去查詢資料庫。
測試步驟:
-
1、開啟日誌
<settings> <!--是否開啟駝峰命名自動映射--> <setting name="mapUnderscoreToCamelCase" value="true"/> <!--標準日誌工廠配置--> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings>
-
2、測試在一個session中查詢兩次相同的sql。
public void test1(){ SqlSession sqlSession = MybatisUtils.getsqlSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); Integer id = 1; User user1 = mapper.queryUserById(id); System.out.println(user1); Integer id2 = 1; System.out.println("====================="); User user2 = mapper.queryUserById(id2); System.out.println(user2); System.out.println("====================="); System.out.println(user1==user2); sqlSession.close(); }
-
3、查看日誌輸出
緩存失效的情況:
-
增刪改操作,可能會改變原來的數據,因此必定會刷新緩存!
-
查詢不同sql(條件不一致也會失效)
-
查詢不同的Mapper.xml
-
手動清理緩存(操作如下,可以看到會執行兩次查詢操作)
sqlSession.clearCache();
小結:
- 一級緩存預設是開啟的,只在一次SqlSession中有效,也就是拿到連接到關閉連接這個區間段!
12.4 二級緩存
- 二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存
- 基於namespace級別的緩存,一個名稱空間,對應一個二級緩存
- 工作機制
- 一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
- 如果當前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的數據會被保存到二級緩存中。
- 新的會話查