MyBatis
什么是MyBatis
-
MyBatis是優秀的持久層框架
-
MyBatis使用XML將SQL與程式解耦,便于維護
-
MyBatis學習簡單,執行高效,是JDBC的延伸
1.MyBatis開發流程
-
引入MyBatis依賴
-
創建核心組態檔
-
創建物體(Entity)類
-
創建Mapper映射檔案
-
初始化SessionFactory
-
利用SqlSession物件操作資料
1.1引入MyBatis依賴
利用maven直接從倉庫匯入即可,有的小伙伴肯定不知道怎么去官網找,先來教一下好了,
官網鏈接:Maven Repository: maven (mvnrepository.com)
直接搜索mybatis然后點擊第一個就好了

這里我們選擇3.5.1版本

往下滑我們可以看到他的配置資訊,復制到pom.xml即可

關于配置pom.xml時IDEA報錯解決方案
有時候我們的idea會抽風,復制過來之后會報紅或者顯示無法在中央倉庫中找到依賴,一般剪切掉它再貼一遍就好了,實在不行重啟一下idea
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>Mybatis</artifactId>
<version>3.5.1</version>
</dependency>
?
//mysql包順便也放這里了,如果使用mysql的同學記得加上
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
1.2創建核心組態檔
差不多和Spring的組態檔差不多格式
<?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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/com.mysql.jdbc.Driver"/>
<property name="url" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/jdbc:mysql://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/root"/>
<property name="password" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/20030515"/>
</dataSource>
</environment>
<!-- 在這里可以配置多個環境,比如生產環境,只需要切換第8行的default為每個環境的id即可-->
<environment id="produce">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/com.mysql.cj.jdbc.Driver"/>
<property name="url" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/jdbc:mysql://localhost:3306/ajaxdb?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
<property name="username" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/root"/>
<property name="password" value="https://www.cnblogs.com/hanlinyuan/archive/2023/05/25/20030515"/>
</dataSource>
</environment>
</environments>
?
<mappers>
<!--用來配置mapper的xml配置文件,相當于告訴mybatis我的用于sql陳述句的組態檔放哪里-->
<mapper resource="mapper/ajaxdb.xml"/>
</mappers>
</configuration>
?
1.3創建物體(Entity)類
這里不再贅述,跟之前的JDBC連接資料庫時要創建出一個跟表中欄位相同的類是一樣的,韓順平Spring體系化筆記(內含ioc,aop,動態代理等底層原理) - 翰林猿 - 博客園 (cnblogs.com)
具體可以查看本文中第10小節,不過既然都學到了mybatis想必對此熟悉的不能再熟悉了,
1.4創建Mapper映射檔案
<?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">
<!--namespace相當于一個包名,這個包下放了很多種sql陳述句,每種又有自己的id,
比如說到時候要使用select * from user時就直接填入user.selectAll即可-->
<mapper namespace="user">
<!--id 相當于給這段用于select的sql陳述句取一個別名,到時候直接用selectAll代替 select * from user 這句話-->
<!--resultType中填寫entity類的全路徑-->
<select id="selectAll" resultType="entity.ajaxdbEntity">
select * from user;
</select>
</mapper>
1.5初始化SessionFactory
在這里我們順便撰寫一個工具類
package utils;
import entity.ajaxdbEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
?
import java.io.IOException;
import java.io.Reader;
import java.util.List;
?
/**
* @Author: 翰林猿
* @Description: TODO
**/
public class MyBatisUtils {
/**
* MyBatisUtils工具類,創建全域唯一的SqlSessionFactory物件
*/
?
//利用static(靜態)屬于類不屬于物件,且全域唯一
private static SqlSessionFactory sqlSessionFactory = null;
?
//利用靜態塊在初始化類時實體化sqlSessionFactory
static {
Reader reader = null;
try {
這里的Resources
reader = Resources.getResourceAsReader("mybatisConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
e.printStackTrace();
//初始化錯誤時,通過拋出例外ExceptionInInitializerError通知呼叫者
throw new ExceptionInInitializerError(e);
}
}
?
/**
* openSession 創建一個新的SqlSession物件
* SqlSession物件類似于JDBC中的Connection
* @return SqlSession物件
*/
public static SqlSession openSession() {
return sqlSessionFactory.openSession();
}
?
/**
* 釋放一個有效的SqlSession物件
*類似于JDBC中釋放獲取到的Connection
* @param session 準備釋放SqlSession物件
*/
public static void closeSession(SqlSession session) {
if (session != null) {
session.close();
}
}
//簡單測驗一下
@Test
public void test(){
//類似于獲取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectAll");
for (ajaxdbEntity dao : ajaxdbDaoList) {
System.out.println(dao.getId());
System.out.println(dao.getUsername());
}
}
}
?
mybatis底層實作(造輪子)

2.SQL
SQL傳參及查詢
回憶之前的JDBC中在組織sql陳述句時 使用問號動態傳入引數 進行查詢的操作,同樣我們mybatis也有,
<!--parameterType也就是傳入的引數的型別是什么-->
<select id="selectList" resultType="entity.ajaxdbEntity" parameterType="Map">
select * from user
where id between #{min} and #{max}
order by id;
</select>
這里我們使用map的形式作為引數傳遞,那么怎么使用呢,修改一下我們的 Test 函式作為示例,
@Test
public void test(){
//類似于獲取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
HashMap map = new HashMap();
map.put("min",1);
map.put("max",3);
//mybatis底層會將傳入的map決議,找到#{key}對應的value填入sql陳述句中
List<ajaxdbEntity> ajaxdbDaoList = sqlSession.selectList("user.selectList",map);
for (ajaxdbEntity map : ajaxdbDaoList) {
System.out.println(map);
}
}
多表查詢
多表查詢時我們不再需要填入parameterType,而且resultType使用Map或者LinkHashMap
使用Map的話,查詢出來的欄位順序是混亂的,具體看Map的底層原理,而LinkHashMap是按順序的,
但是這種查詢方式有一個很大的缺點,你應該也已經發現了,為什么我們的resultType不再是物體Entity類了?
這種查詢方式不需要經過驗證,他什么東西都可以直接查詢出來,所以在企業中開發大型專案還是一般不使用這種方式,
<select id="selectTwice" resultType="Map">
select * from user,user2;
</select>
所以我們使用ResultMap結果映射,來解決一下這個問題,
ResultMap結果映射查詢
作用:首先是完成資料庫欄位與物體類欄位的映射(類似之前為了解決資料庫欄位和物體類欄位不相同采用AS陳述句完成映射),其次就是解決上面所提到的問題,
說白了,還是Entity那套,再創建一個類,作為多表查詢的物體類,只不過這個類里有多個entity的欄位罷了,那么我們來寫一下這個欄位吧,
package entity;
?
/**
* @Author: 翰林猿
* @Description: 多表查詢物體類
**/
public class UserDTO {
private user user = new user(); //user表
private String cn; //user2表中的cn欄位(ChineseName的縮寫)
?
@Override
public String toString() {
return "UserDTO{" +
"user=" + user +
", cn='" + cn + '\'' +
'}';
}
?
public user getUser() {
return user;
}
?
public void setUser(user user) {
this.user = user;
}
?
public String getCn() {
return cn;
}
?
public void setCn(String cn) {
this.cn = cn;
}
?
public UserDTO() {
}
?
public UserDTO(user ajaxdbEntity, String cn) {
user = ajaxdbEntity;
this.cn = cn;
}
}
?
好,我們再來看看基本使用方法,我們要定義一個resultMap標簽,id值就是作為這段resultMap的別名,type是最后查詢回傳的類的全路徑,然而里面又有很多種標簽,這里我們就介紹2個,其他的請大家自己前往mybatis官網查看教程,
我們的id和result標簽中又包括兩個引數,property以及column
-
property :填寫的必須與上面的type中的類的欄位完全相同,(也就是UserDTO中的欄位),其中有一個欄位是user類的,那么為了拿到user類中的欄位直接使用 . 運算子即可,
-
column :資料庫中的列名,或者是列的別名,
<resultMap id="selectDTO" type="entity.UserDTO">
<id property="user.id" column="id"></id>
<result property="user.username" column="username"/>
<result property="user.pwd" column="pwd"/>
<result property="user.email" column="email"/>
<result property="cn" column="cn"/>
property對應物體類的屬性 ,colum對應資料庫的欄位
</resultMap>
?
<select id="selectTwice" resultMap="selectDTO">
select user.* , cn from user,user2;
</select>
/**
* @Author: 翰林猿
* @Description: 多表查詢物體類,測驗
**/
@Test
public void test(){
//類似于獲取Connection
SqlSession sqlSession = MyBatisUtils.openSession();
List<UserDTO> ajaxdbDaoList = sqlSession.selectList("User.selectTwice");
for (UserDTO map : ajaxdbDaoList) {
System.out.println(map);
}
}
SQL插入
我們在寫sql插入陳述句的時候有時候要寫很多列名,真的是非常令人煩惱啊,很顯然,mybais開發人員也想到了這點,用foreach標簽節約開發時間,
foreach元素的屬性主要有 item,index,collection,open,separator,close,
-
item表示集合中每一個元素進行迭代時的別名,
-
index指 定一個名字,用于表示在迭代程序中,每次迭代到的位置,
-
open表示該陳述句以什么開始,
-
separator表示在每次進行迭代之間以什么符號作為分隔符,
-
close表示以什么結束,
<!--INSERT INTO table-->
<!--VALUES ("a" , "a1" , "a2"),("b" , "b1" , "b2"),(....)-->
<insert id="batchInsert" parameterType="java.util.List">
INSERT INTO t_goods(title, sub_title, original_cost, current_price, discount, is_free_delivery, category_id)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(#{item.title},#{item.subTitle}, #{item.originalCost}, #{item.currentPrice}, #{item.discount}, #{item.isFreeDelivery}, #{item.categoryId})
</foreach>
</insert>
注意新增資料之后,要記得提交事務
/**
* 新增資料,示范用例
*/
@Test
public void testInsert() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Goods goods = new Goods();
goods.setTitle("測驗商品");
goods.setSubTitle("測驗子標題");
goods.setOriginalCost(200f);
goods.setCurrentPrice(100f);
goods.setDiscount(0.5f);
goods.setIsFreeDelivery(1);
goods.setCategoryId(43);
//insert()方法回傳值代表本次成功插入的記錄總數
int num = session.insert("goods.insert", goods);
session.commit();//提交事務資料
System.out.println(goods.getGoodsId());
}catch (Exception e){
if(session != null){
session.rollback();//回滾事務
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
SQL洗掉
洗掉同理
<delete id="batchDelete" parameterType="java.util.List">
DELETE FROM t_goods WHERE goods_id in
<foreach collection="list" item="item" index="index" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
/**
* 洗掉資料
*/
@Test
public void testDelete() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
int num = session.delete("goods.delete" , 739);
session.commit();//提交事務資料
}catch (Exception e){
if(session != null){
session.rollback();//回滾事務
}
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
動態SQL
在我們逛淘寶的時候,是否有發現在搜索商品時有很多標簽可以選擇,比如說指定某個品牌,某種型別等等,其實從開發者的角度上來看,是不是還是一個SQL陳述句罷了,只不過多加了幾個引數,但是問題就在于,這些引數如何動態的加載進去,在想要的時候才用他呢?
這里引出mybatis的動態SQL陳述句技術,不過是加個標簽罷了,底層依舊是之前那套,dom4j+動態代理+反射完成的,
<select id="dynamicSQL" parameterType="java.util.Map" resultType="com.imooc.mybatis.entity.Goods">
select * from t_goods
<where> //SQL種的where改成使用where標簽
<if test="categoryId != null"> //如果map里有這個引數,就加上下面這句
and category_id = #{categoryId}
</if>
<if test="currentPrice != null">
and current_price < #{currentPrice}
</if>
</where>
</select>
/**
* 動態SQL陳述句
*/
@Test
public void testDynamicSQL() throws Exception {
SqlSession session = null;
try{
session = MyBatisUtils.openSession();
Map param = new HashMap();
param.put("categoryId", 44);
param.put("currentPrice", 500);
//查詢條件
List<Goods> list = session.selectList("goods.dynamicSQL", param);
for(Goods g:list){
System.out.println(g.getTitle() + ":" +
g.getCategoryId() + ":" + g.getCurrentPrice());
?
}
}catch (Exception e){
throw e;
}finally {
MyBatisUtils.closeSession(session);
}
}
3.Mybatis二級快取機制
為了提高查詢效率,減少資料庫的訪問次數,Mybatis采用了兩層快取機制,并分為一級快取和二級快取
因為一級快取的命中率可能較低,所以還有一層二級快取
-
一級快取默認開啟,快取范圍SqlSession
-
(換句話說就是,只在SqlSession session = MyBatisUtils.openSession()的這個session里有效,當我們寫了兩遍這個代碼,就是兩個不同的SqlSession物件)
-
拿上面的代碼舉例,就是你使用這個session不管查詢多少次相同的陳述句,都是從快取里拿出來的一個結果
session.selectList("goods.dynamicSQL", param); session.selectList("goods.dynamicSQL", param); session.selectList("goods.dynamicSQL", param); //最后都是相同的結果,記憶體地址都是一樣的 -
-
二級快取手動開啟,屬于范圍Mapper Namespace
-
(換句話說,就是只要是使用了之前我們定義mapper的namespace中的SQL陳述句里都有效)
<mapper namespace="goods"> ....各種SQL.... </mapper> -

注意:
當呼叫SqlSession的修改、添加、洗掉、commit()、close()等方法時,為了保證資料的一致性,mybatis會強制清空一級快取,
如何理解這句話?用代碼舉例一下
session = MyBatisUtils.openSession();
Goods goods = session.selectOne("goods.selectById" , 1603);
session.commit(); //commit提交時對該namespace快取強制清空
Goods goods2 = session.selectOne("goods.selectById" , 1603);
這個時候,因為提交過一次事務,所以第二次的查詢goods2時的hashcode與第一次的goods其實是不一樣的,本質上是2次查詢,而不是直接拿出goods已經查詢出來的內容賦給goods2
開啟二級快取
在xml中的mapper配置一句話即可
<mapper namespace="goods">
<!--
eviction是快取的清除策略,當快取物件數量達到上限后,自動觸發對應演算法對快取物件清除
1.LRU – 最近最久未使用:移除最長時間不被使用的物件,
O1 O2 O3 O4 .. O512 最多只有512個物件
14 99 83 1 893(秒) 記錄物件上一次被訪問的時間,如果512個滿了,就會先把時間最長的893秒的那個物件移除
2.FIFO – 先進先出:按物件進入快取的順序來移除它們,
3.SOFT – 軟參考:移除基于垃圾收集器狀態和軟參考規則的物件,
4.WEAK – 弱參考:更積極的移除基于垃圾收集器狀態和弱參考規則的物件,
?
flushInterval屬性表示重繪快取的時間間隔ms
size屬性表示快取的大小
readOnly屬性表示快取是否只讀,可選值為true或false,true是回傳物件本身(效率高),false則是回傳物件的副本(安全好)
-->
<cache eviction="LRU" flushInterval="600000" size="512" readOnly="true"/>
....各種SQL....
</mapper>
快取大量資料性能問題
既然我們開啟了快取,那么就要考慮一下如果快取了大量資料影響性能怎么辦,其實對于查詢大量資料的陳述句可以不使用快取,而且在實際程序中,這種大量的查詢也不會經常復用,
<!-- useCache="false"代表該陳述句不保存至快取 -->
<select id="selectAll" resultType="com.imooc.mybatis.entity.Goods" useCache="false">
select * from t_goods order by goods_id desc limit 10
</select>
<!-- flushCache="true"執行完這句話馬上重繪快取 = 一個commit操作 -->
<!-- 并且這句話查詢出來的資料本身也不會放入快取 -->
<select id="selectGoodsMap" resultType="java.util.LinkedHashMap" flushCache="true">
select g.* , c.category_name,'1' as test from t_goods g , t_category c
where g.category_id = c.category_id
</select>
4.物件關聯查詢(一對多、多對一)
我們知道,一個表內有很多個物體,可能有一個物體對應了很多個物件,有一些則是一對一,多對一,多對多等等,

所以引出我們的關聯查詢
一對多查詢:
先來舉個例子:比如說我們要查詢一個商品,但是一個商品Goods又對應了很多個商品細節GoodsDetail(一對多)
所以我們的商品細節表GoodsDetail要持有我們的商品表Goods的主鍵 goodsId,那么除掉建表的程序,先寫一下對應的物體類吧
public class Goods {
private Integer goodsId;//商品編號
private String title;//標題
private String subTitle;//子標題
private Float originalCost;//原始價格
private Float currentPrice;//當前價格
private Float discount;//折扣率
private Integer isFreeDelivery;//是否包郵 ,1-包郵 0-不包郵
private Integer categoryId;//分類編號
private List<GoodsDetail> goodsDetails;
}
//省略getter,setter,構造器等等
public class GoodsDetail {
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
private Goods goods;
}
//省略getter,setter,構造器等等
那么我們要如何實作一對多的查詢呢,我們來看看本質是什么,不過就是去Goods里面找GoodsDetail,再去GoodsDetail里面找具體的屬性罷了,
所以,還是使用我們的resultMap進行映射就好了,只要將goodsDetails的屬性映射到Goods里面,不就相當于Goods擁有了goodsDetails的屬性了嘛,可以理解為類似下面這個類,
public class GoodsAfterMapping {
private Integer goodsId;//商品編號
private String title;//標題
private String subTitle;//子標題
private Float originalCost;//原始價格
private Float currentPrice;//當前價格
private Float discount;//折扣率
private Integer isFreeDelivery;//是否包郵 ,1-包郵 0-不包郵
private Integer categoryId;//分類編號
private Integer gdId;
private Integer goodsId;
private String gdPicUrl;
private Integer gdOrder;
private Goods goods;
}
//省略getter,setter,構造器等等
ok,有了思路,來具體實作一下吧,寫好查詢陳述句
<mapper namespace="goodsDetail">
<select id="selectOneToMany" resultMap="RmGoods1">
select * from t_goods limit 0,10
</select>
</mapper>
想一想我們的思路,是不是要利用這句select陳述句查詢出來的ID,去我們的我們商品細節表GoodsDetail里把其他屬性也查出來,所以我們再寫一個select陳述句用于查詢商品細節表GoodsDetail
<mapper namespace="goodsDetail">
<select id="selectByGoodsId" parameterType="Integer"
resultType="com.imooc.mybatis.entity.GoodsDetail">
select * from t_goods_detail where goods_id = #{value}
</select>
</mapper>
然后開始配置我們的 resultMap 取名為 RmGoods1
<mapper namespace="goodsDetail">
<!--
resultMap可用于說明一對多或者多對一的映射邏輯
id 是resultMap屬性參考的標志
type 指向我們所謂的一對多的一的物體(也就是Goods)
-->
<resultMap id="RmGoods1" type="com.imooc.mybatis.entity.Goods">
<!-- 還是先確定主鍵id,把資料庫欄位和物體類同步一下 -->
<id column="goods_id" property="goodsId"></id>
<!--這里不需要像多表查詢那里大量配置result,因為資料庫欄位和POJO欄位符合駝峰命名規則,框架會自動轉換-->
<!--collection的含義是,在select * from t_goods limit 0,1 得到結果后,
將得到的goods_id值,
代入到goodsDetail命名空間的selectByGoodsId的SQL中執行查詢,
最后框架會自動將查詢得到的"商品細節"的結果(一個集合)
賦值給 private List<GoodsDetail> goodsDetails 這個物件 -->
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
</mapper>
為了好看放一下全部配置吧
<?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="goodsDetail">
查詢Goods表
<select id="selectOneToMany" resultMap="rmGoods1">
select * from t_goods limit 0,10
</select>
利用上句查詢出來的ID去GoodsDetail查詢
<select id="selectByGoodsId" parameterType="Integer"
resultType="com.imooc.mybatis.entity.GoodsDetail">
select * from t_goods_detail where goods_id = #{value}
</select>
配置resultMap
<resultMap id="RmGoods1" type="com.imooc.mybatis.entity.Goods">
<id column="goods_id" property="goodsId"></id>
<collection property="goodsDetails" select="goodsDetail.selectByGoodsId" column="goods_id"/>
</resultMap>
</mapper>
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553382.html
標籤:其他
上一篇:springboot~統一處理日期請求引數java.utils.Date和java.time.LocalDate
下一篇:返回列表
