MyBatis 筆記
一、入門
1.1 什麼是 MyBatis?
-
MyBatis 是一款持久層框架(ORM 程式設計思想)
-
MyBatis 免除了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集的過程;
-
MyBatis 可以使用簡單的 XML 或註解來配置和對映原生資訊,將介面和 Java 的實體類(pojo:Plain Old Java Object 簡單的 Java 物件)對映成資料庫中的記錄;
-
MyBatis 前身為 iBatis(經常在控制檯看見);
-
Mybatis官方文件 : http://www.mybatis.org/mybatis-3/zh/index.html
1.2 持久化
持久化是將程式資料在持久狀態和瞬時狀態間轉換的機制
通俗的講,就是瞬時資料(比如記憶體中的資料,是不能永久儲存的)持久化為持久資料(比如持久化至資料庫中,能夠長久儲存)
-
即把資料(如記憶體中的物件)儲存到可永久儲存的儲存裝置中(如磁碟)。持久化的主要應用是將記憶體中的物件儲存到資料庫中,或儲存在磁碟、XML 等檔案中;
-
JDBC就是一種持久化機制。檔案IO也是一種持久化機制。
持久化的意義:
由於記憶體的儲存特性,將資料從記憶體中持久化至資料庫,從儲存成本(容量、儲存時長)看,都是必要的。
1.3 持久層
即資料訪問層(DAL 層),其功能主要是負責資料庫的訪問,實現對資料表的 CEUD 等操作。
持久層的作用是將輸入庫中的資料對映成物件,則我們可以直接通過操作物件來運算元據庫,而物件如何和資料庫發生關係,那就是框架的事情了。
1.4 第一個 MyBatis 程式
使用環境:
jdk8
MySql 5.7
Maven 3.6.3
IEDA
專案結構:
配置 pom.xml,新增必要的依賴:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--父工程
可以用idea根目錄作為工程目錄,也可以其為父工程,每個Module作為工程目錄
優點:父工程配置一次依賴,所有子工程受用
-->
<groupId>org.example</groupId>
<artifactId>Mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>mybatis_01</module>
</modules>
<!-- 設定打包格式-->
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
每個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的例項為核心的,可通過SqlSessionFactoryBuilder 物件獲得,而 SqlSessionFactoryBuilder 則可以從 XML 配置檔案或一個預先配置的 Configuration 例項來構建出 SqlSessionFactory 例項。
第一步:新建 MyBatis 的核心配置檔案 mybatis-config.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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- 每一個Mapper.xml都要在專案的核心配置檔案中註冊對映-->
<mappers>
<mapper resource="yh/dao/UserMapper.xml"/>
</mappers>
</configuration>
第二步:編寫獲取 SqlSession 物件的工具類
package yh.utils;
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 java.io.IOException;
import java.io.InputStream;
/**
* 獲取SqlSession物件的工具類
*
* @author YH
* @create 2020-05-13 11:01
*/
public class MybatisUtils {
//1.獲取SqlSessionFactory物件
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 2.獲取SqlSession物件
*
* @return
*/
public static SqlSession getSqlSession() {
return sqlSessionFactory.openSession();
}
}
第三步:建立對應資料表的實體類
package yh.pojo;
/**
* 實體類
* pojo:簡單的Java物件(Plain Old Java Object)
*
* @author YH
* @create 2020-05-13 11:36
*/
public class User {
private int id;
private String name;
private String pwd;
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 User(int id, String name, String pwd) {
this.id = id;
this.name = name;
this.pwd = pwd;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", pwd='" + pwd + '\'' +
'}';
}
}
第四步:建立 Mapper 介面
package yh.dao;
import yh.pojo.User;
import java.util.List;
/**
* 持久層介面
* 在MyBatis中用Mapper替換原來的Dao
*
* @author YH
* @create 2020-05-13 11:35
*/
public interface IUserMapper {
/**
* 查詢所有
*
* @return
*/
List<User> selectUser();
/**
* 根據id查使用者
* @param id
* @return
*/
User selectUserById(int id);
/**
* 修改使用者資訊
* @param user
*/
int updateUser(User user);
/**
* 刪除使用者
* @param id
*/
int deleteUser(int id);
int addUser(User user);
}
第五步:建立 Mapper.xml 檔案(以前是實現類,顯示是實現一個 source)
<?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名稱空間:指定持久層介面-->
<mapper namespace="yh.dao.IUserMapper">
<!--select標籤:表名是要執行查詢操作,內部填寫SQL語句
id屬性:指定介面中定義的方法
resultType屬性:指定實體類全類名
這一對標籤就像對應介面中的一個方法-->
<select id="selectUser" resultType="yh.pojo.User">
select * from mybatis.user
</select>
<!-- #{} 就像以前的萬用字元,裡面的id就是形參變數
parameterType:設定引數型別-->
<select id="selectUserById" parameterType="int" resultType="yh.pojo.User">
select * from mybatis.user where id=#{id}
</select>
<update id="updateUser" parameterType="yh.pojo.User">
update mybatis.user set name=#{name},pwd=#{pwd} where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from mybatis.user where id=#{id}
</delete>
<insert id="addUser" parameterType="yh.pojo.User">
insert into mybatis.user values(#{id},#{name},#{pwd});
</insert>
</mapper>
每一個 Mapper.xml 都需要在 mybatis 核心配置檔案中註冊
第五步:編寫測試類
按照規範,test 目錄下測試類的結構要與 java 程式碼結構對應
package yh.dao;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import yh.pojo.User;
import yh.utils.MybatisUtils;
import java.util.List;
/**
* 測試類
*
* @author YH
* @create 2020-05-13 12:13
*/
public class UserMapperTest {
@Test
public void selectUser() {
//1.獲取SqlSession物件(用來執行SQL,像以前用的ProperStatement)
SqlSession session = MybatisUtils.getSqlSession();
//通過反射從獲取物件
IUserMapper mapper = session.getMapper(IUserMapper.class);
//呼叫例項方法
List<User> users = mapper.selectUser();
for (User u : users) {
System.out.println(u);
}
session.close();
}
@Test
public void selectUserById(){
SqlSession session = MybatisUtils.getSqlSession();
IUserMapper mapper = session.getMapper(IUserMapper.class);
User user = mapper.selectUserById(2);
System.out.println(user);
session.close();
}
@Test
public void updateUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User user = new User(2, "熊大", "123");
int i = mapper.updateUser(user);
if(i > 0){
System.out.println("修改成功");
}
//增刪改查操作需要提交事務
sqlSession.commit();
sqlSession.close();
}
@Test
public void deleteUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
int i = mapper.deleteUser(3);
if(i > 0){
System.out.println("刪除成功");
}
//增刪改查操作需要提交事務
sqlSession.commit();
sqlSession.close();
}
@Test
public void insterUser(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
User user = new User(5, "熊二", "321");
int i = mapper.addUser(user);
if(i > 0){
System.out.println("插入成功");
}
//增刪改查操作需要提交事務
sqlSession.commit();
sqlSession.close();
}
}
查詢所有結果:
資料表資料:
-
MyBatis 應用的增刪增刪改操作需要提交事務,傳統 JDBC 的增刪改操操作中,連線物件被建立時,預設自動提交事務,在執行成功關閉資料庫連線時,資料就會自動提交。
-
萬能 Map
用 map 集合來儲存執行 SQL 所需的引數,多個引數時也可用 Map 或使用註解。
應用場景:假如通過 new 物件作為引數,呼叫修改方法,new一個物件需要填上構造器的所有引數,而我們可能只需要用到其中一個,就很麻煩,而使用 map 可製造任意引數,key 為引數名,value 為引數值:
三種傳參方式:
直接傳值 如
...method(int id)
,可以直接在 sql 中取id
; 物件作為引數傳遞 如
...method(User user)
,直接在 sql 中去物件的屬性即可; Map 作為引數 如
...method(Map<String,Object> map)
,直接在 sql 中取出 key 即可 -
可能出現的問題:
- 分析錯誤異常資訊,從下往上讀
- 找不到資源異常:
原因:Maven 會對靜態資源過濾,即在 java 目錄下的非 java 程式碼都不編譯
解決:在 pom.xml 中配置resources:
<!-- build中配置resources標籤,針對無法找到java目錄下的資源問題--> <build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.properties</include> <include>**/*.xml</include> </includes> <filtering>false</filtering> </resource> </resources> </build>
-
Mapper.xml 沒有在核心配置檔案(mybatis-config.xml 中)註冊,解決新增如下配置:
<mappers> <mapper resource="yh/dao/UserMapper.xml"/> </mappers>
-
其他問題往往是出現在與資料庫連線的配置上,如 url 配置等
-
補充:
模糊查詢寫法:
-
在 Java 程式碼層面,傳引數值的時候寫萬用字元 % %
List<User> users = mapper.getUserLike("%李%");
-
在 sql 拼接中使用萬用字元
select * from mybatis.user where name like "%"#{value}"%"
-
二、XML 配置
MyBatis 核心配置檔案頂層結構:
新增對應元素時需要按照順序來(如配置 properties 元素,要將其放在最上面);
屬性(properties)
我們在配置資料來源(DataSource)的時候設定的 driver、url、username 等都可以使用配置屬性的形式獲取(這樣資料來源就可以以一個固定模板呈現,資料來源修改是方便些)。設定:
<properties resource="db.properties">
<!-- 同時可以在內部設定屬性(隱私性更好)-->
<property name="username" value="root"/>
<property name="password" value="root"/>
</properties>
讀取外部可動態改變的 properties檔案,不用修改主配置類就可以實現動態配置
Mybatis讀取配置的順序:
先讀取properties元素體內屬性;再讀取resources/url屬性中指定屬性檔案;最後讀取作為方法引數傳遞的屬性;**
讀取到同名屬性時,先讀取到的倍後讀取到的覆蓋;
所以,這幾個位置讀取屬性的優先順序:
作為方法引數傳遞的屬性 > resources/url屬性中指定屬性檔案 > properties元素體內屬性
通過標識被讀取的資料,DataSource中可以直接通過name引用,如下:
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
配置 JDBC 連線所需引數值的都用變數代替了。
設定(setting)
型別別名(typeAliases)
別名使用資料庫都瞭解過,字元段太長了可使用別名代替;同理,Mybatis 中嚐嚐應用類的全限定類名(全類名),往往都很長,可以使用別名的形式讓我們引用全類名更加便利:
型別別名的作用域:當前 xml 檔案中
<typeAliases>
<!-- 給指定全類名的類其別名-->
<typeAlias type="yh.pojo.User" alias="User"/>
<!-- 給指定包下所有的JavaBean起別名,別名為其類名首字母小寫->
<package name="yh.dao"/>
</typeAliases>
配置後,使用全限定類名的地方都可以用別名替換(如,User
可以使用在任何使用 yh.pojo.User
的地方。
注意:給包起別名,預設別名是包下JavaBean的首字母小寫,如果這個JavaBean有註解的話,則別名為其註解值,如下:
@Alias("User1")
public class User {
...
}
Mybatis 中給常見的 Java 型別內建了別名,整體規律:基本型別別名為在前面加一個下劃線 '_';而包裝類的別名為其對應的基本型別名(也就是說我們使用 int 作為 resultType 引數值時,實際使用的是 Integer,所以我們設定方法引數時,最好也是用 Integer,沒報錯應該是自動裝箱的功勞)。
對映器(Mappers)
有四種方式:
<mappers>
<!-- 指定相對於類路徑的資源引用(推薦使用)-->
<mapper resource="yh/dao/UserMapper.xml"/>
<!-- 使用完全限定資源定位符(URL) -->
<mapper url="file:///var/mapper/UserMapper.xml"/>
<!-- 使用對映器介面實現類的完全限定類名 前提:介面名與xml檔名名相同-->
<mapper class="yh.dao.IUserMapper"/>
<!-- 將包內的對映器介面實現全部註冊為對映器 前提:介面名與xml檔名名相同-->
<package name="yh.dao"/>
</mappers>
這些配置會告訴 MyBatis 去哪裡找對映檔案,剩下的細節就應該是每個 SQL 對映檔案了.
生命週期 和 作用域(Scope)
作用域和生命週期類別至關重要,因為錯誤的使用會導致非常嚴重的併發問題。
一次完整的生命週期:
- SqlSessionFactoryBuilder
- 一旦建立了 SQLSessionFactory 工廠物件,就不再需要了
- 作用域:區域性方法變數
- SqlSessionFactory
- 一旦被建立,在執行期間就一直存在
- 作用域:全域性(應用作用域)
- 最簡單的就是使用單例模式或者靜態單例模式。
- 就像資料庫中的連線池,有請求就給一個連線。同理多執行緒時,有請求就建立一個 SqlSession,示意圖如下:
-
SqlSession
-
每個執行緒都有獨有的 SqlSession 例項,其執行緒時不安全的,因此不能被共享(多例)
-
作用域:請求或方法作用域
-
如在 web 中,一次請求就開啟一個 SqlSession,返回一個響應後,就關閉它,如:
try (SqlSession session = sqlSessionFactory.openSession()) { // 你的應用邏輯程式碼 }
-
三、XML 對映器
cache
– 該名稱空間的快取配置。cache-ref
– 引用其它名稱空間的快取配置resultMap
– 描述如何從資料庫結果集中載入物件,是最複雜也是最強大的元素。sql
– 可被其它語句引用的可重用語句塊。insert
– 對映插入語句。update
– 對映更新語句。delete
– 對映刪除語句。select
– 對映查詢語句。
結果對映(resultMap)
解決列名與 JavaBean 屬性名不匹配問題。
ResultMap 設計思想:對簡單語句做到零配置,對複雜語句,只需要描述語句間的關係就行了。
方式一:零配置
如前面提到的萬能 Map 將列對映到 HashMap
的鍵上,由resultType
屬性指定以及直接對映到物件(即對映到 ResultSet ,如:resultType="User"
)這些都是簡單的對映,MyBatis 底層會自動建立一個 ResultMap
,再根據屬性名來對映列到 JavaBean 的屬性上;
通過 SQL 語句設定別名也可以實現匹配
方式二:描述語句間的關係
- 先在
Mapper.xml
檔案的mapper
標籤內顯示配置resultMap
:
<!--id屬性:此resultMap的標識,供引用語句指定
type屬性:對映JavaBean全類名(可用別名)-->
<resultMap id="userResultMap" type="yh.pojo.User">
<result column="id" property="id"/>
<result column="name" property="name"/>
<!-- 以上兩條是匹配的可以省略。就寫不匹配的那個屬性,如下-->
<result column="password" property="pwd"/>
</resultMap>
- 在引用它的語句中設定
resultMap
屬性即可:
<select id="selectUsers" resultMap="userResultMap">
select id, name, password
from user
where id = #{id}
</select>
四、日誌
日誌工廠
MyBatis 核心配置檔案中的 settings
元素的 logImpl
屬性用於 指定 MyBatis 所用日誌的具體實現,未指定時將自動查詢。
引數值:
SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
- STDOUT_LOGGING:標準日誌輸出
在核心檔案 mybatis-config.xml 中進行如下配置:
<settings>
<!--標準的日誌工程實現-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
標準日誌輸出(STDOUT_LOGGING)測試結果:
LOG4J
-
Java 日誌框架,通過它可以控制日誌資訊輸送的目的地為控制檯、檔案還是 GUI 元件等;
-
可以控制每一條日誌的輸出格式;
-
通過定義每一條日誌資訊的級別,我們能夠更加細緻地控制日誌的生成過程;
-
通過一個配置檔案來靈活地進行配置,而不需要修改應用的程式碼。
-
配置:
-
新增依賴
<dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
resource
資源目錄下新建 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/yh.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{yy-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
-
配置 log4j 為日誌的實現
<settings> <!-- log4j日誌實現--> <setting name="logImpl" value="LOG4J"/> </settings>
-
測試結果:
-
簡單使用:在要輸出日誌的類中加入相關語句:
-
在要使用 Log4j 的類中,匯入包
org.apache.log4j.Logger
-
然後獲取日誌物件,引數為當前類的 class,如下:
static Logger logger = Logger.getLogger(UserMapperTest.class);
-
獲取物件後,在要使用它的方法中通過日誌物件根據需要的日誌級別呼叫對應方法,引數為需要提示的資訊(像以前使用 print 輸出提示),如下:
logger.info("我是info級別的日誌訊息"); logger.debug("我是debug級別的日誌訊息"); logger.error("我是error級別的日誌訊息");
-
測試結果:
控制檯輸出:
輸出到 file 檔案中:
-
五、分頁
使用 Limit 分頁
分頁核心由 SQL 完成。
-
介面中定義:
/** * 查詢結果集分頁 * @param map * @return */ List<User> selectUserLimit(Map<String,Integer> map);
-
Mapper.xml
<select id="selectUserLimit" parameterType="map" resultMap="userResultMap"> select * from mybatis.user limit #{index},#{num} </select>
-
測試
@Test public void testLimit(){ SqlSession session = MybatisUtils.getSqlSession(); IUserMapper mapper = session.getMapper(IUserMapper.class); HashMap<String,Integer> map = new HashMap<>(); map.put("index",1); map.put("num",2); List<User> users = mapper.selectUserLimit(map); for(User u : users){ System.out.println(u.toString()); } session.close(); }
-
結果
RowBounds 分頁(瞭解)
不使用 SQL 實現分頁。
-
介面
/** * 查詢結果集分頁 * @param map * @return */ List<User> selectUserLimit(Map<String,Integer> map);
-
mybatis.xml
<select id="selectUserLimit" resultMap="userResultMap"> select * from mybatis.user </select>
-
測試
@Test public void testRowBounds(){ SqlSession session = MybatisUtils.getSqlSession(); //RowBounds實現 RowBounds rowBounds = new RowBounds(1,2); //通過java程式碼層面實現分頁 List<User> userList = session.selectList("yh.dao.IUserMapper.selectUserLimit",null,rowBounds); //遍歷輸出:略... session.clise(); }
分頁外掛
MyBatis 分頁外掛:PageHelper
官方文件:https://pagehelper.github.io/
五、使用註解
簡單查詢:
- MyBatis 中使用註解可以省去實現介面的 xml 檔案,直接加一條註解語句,如下:
public interface IUserMapper {
@Select("select * from user")
List<User> selectUser();
}
- 而在 mybatis 核心配置檔案中的
mappers
元素中註冊繫結介面:
<mappers>
<mapper class="yh.dao.IUserMapper"/>
</mappers>
- 測試:
@Test
public void testAnnSelect(){
SqlSession session = MybatisUtils.getSqlSession();
IUserMapper mapper = session.getMapper(IUserMapper.class);
List<User> users = mapper.selectUser();
for (User user : users) {
System.out.println(user.toString());
}
session.close();
}
結果:
MyBatis 中,簡單的 sql 語句可使用註解對映,複雜的最好用 xml 配置,否則難上加難;不要拘泥於某種方式,可兩者配合著使用。
註解實現簡單 CRUD
注意:MybatisUtils
工具類做了以下修改:
/**
* 獲取SqlSession物件
* @return
*/
public static SqlSession getSqlSession(){
//造物件並設定其自動提交事務
return sqlSessionFactory.openSession(true);
}
demo 的結構:
-
編寫介面,使用註解
package yh.dao; import org.apache.ibatis.annotations.*; import yh.pojo.User; import java.util.List; /** * @author YH * @create 2020-05-15 10:51 */ public interface IUserMapper { /** * 新增使用者 * @param user */ @Insert("insert into user(id,name,pwd) values(#{id},#{name},#{password})") int addUser(User user); /** * 刪除使用者 * @param id * @return */ @Delete("delete from user where id=#{id}") int seleteUser(@Param("id") int id); /** * 修改使用者 * @param user * @return */ @Update("update user set name=#{name},pwd=#{password} where id=#{id}") int updateUser(User user); /** * 查詢所有 * @return */ @Select("select * from user") List<User> selectUser(); }
關於 @Param()
註解:
- 基本型別的引數或者 String 型別需要加上
- 引用型別不需要加
- 如果只有一個基本型別,可以可以省略(建議加上)
- 我們在 SQL 中引用的,以
@Param()
中的設定為準(如引數變數與設定不同時)
-
而在 mybatis 核心配置檔案中的
mappers
元素中註冊繫結介面:<mappers> <mapper class="yh.dao.IUserMapper"/> </mappers>
{} 與 ${}:常用前者(sql 解析時會加上" ",當成字串解析;後者傳入資料直接顯示在生成的 sql 中,無法防止 SQL 注入。
-
測試
package yh.dao; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import yh.pojo.User; import yh.utils.MybatisUtils; import java.util.List; /** * @author YH * @create 2020-05-16 10:39 */ public class testAnn { @Test public void addUser(){ SqlSession session = MybatisUtils.getSqlSession(); IUserMapper mapper = session.getMapper(IUserMapper.class); int i = mapper.addUser(new User(6, "葫蘆娃", "12333")); //由於工具類中設定了自動提交事務,所以這邊可以省略 if (i > 0) { System.out.println("增加成功"); } session.close(); } @Test public void seleteUser(){ SqlSession session = MybatisUtils.getSqlSession(); IUserMapper mapper = session.getMapper(IUserMapper.class); int i = mapper.seleteUser(1); //由於工具類中設定了自動提交事務,所以這邊可以省略 if (i > 0) { System.out.println("刪除成功"); } session.close(); } @Test public void updateUser(){ SqlSession session = MybatisUtils.getSqlSession(); IUserMapper mapper = session.getMapper(IUserMapper.class); int i = mapper.updateUser(new User(4, "奧特曼", "11111111")); if(i > 0){ System.out.println("修改成功"); } session.close(); } @Test public void testAnnSelect(){ SqlSession session = MybatisUtils.getSqlSession(); IUserMapper mapper = session.getMapper(IUserMapper.class); List<User> users = mapper.selectUser(); for (User user : users) { System.out.println(user.toString()); } session.close(); } }
複雜查詢
多對一
如多個學生被一個老師教,對學生而言是多對一的關係,那麼怎麼對它們進行查詢呢?
如何通過查詢學生表,同時獲取到他的老師的資訊?
單純用 sql 語句的話,連線查詢、子查詢很簡單就可以實現,但是要怎麼在 mybatis 中實現呢?兩種方式:
前言
多對一情況下,兩個 JavaBean 的屬性:
public class Student {
private int id;
private String name;
/**
* 多個學生同一個老師,即多對一
*/
private Teacher teacher;
//略...
}
public class Teacher {
private int id;
private String name;
//略...
}
方式一:按照查詢巢狀處理(子查詢)
先查詢出所有學生的資訊,根據查詢出來的tid(外來鍵),尋找對應的老師。具體實現:
學生介面的 Mapper.xml:
<mapper namespace="yh.dao.IStudentMapper">
<select id="selectStudents" resultMap="StudentResultMap">
select * from mybatis.student
</select>
<resultMap id="StudentResultMap" type="yh.pojo.Student">
<result property="id" column="id"/>
<result property="name" column="name"/>
<!--上面的語句也可以省略(JavaBean的屬性名和表的欄位名可以匹配)
複雜的屬性需要單獨處理:
處理物件:association
處理集合:collection
-->
<association property="teacher" column="tid" javaType="yh.pojo.Teacher" select="selectTeachers"/>
</resultMap>
<select id="selectTeachers" resultType="yh.pojo.Teacher">
select * from mybatis.teacher where id=#{tid}
</select>
</mapper>
使用子查詢實現,自然需要兩次查詢,關鍵就是如何將兩次查詢關聯起來,這就用到 mpper
元素的子標籤:association
元素,property
為實體類對應的屬性,column
為表中對應的欄位(外來鍵),javaType
:查詢結果對應的 JavaBean 全類名,select
關聯查詢語句
測試:
@Test
public void selectStudent(){
SqlSession session = MybatisUtils.getSqlSession();
IStudentMapper mapperS = session.getMapper(IStudentMapper.class);
List<Student> students = mapperS.selectStudents();
for (Student student : students) {
System.out.println(student.toString());
}
session.close();
}
結果:
方式二:按照結果巢狀處理(對連線查詢的結果表進行處理)
先執行sql語句進行連線查詢,獲取結果(表),再通過對結果表的處理實現mybatis中多對一查詢
學生介面的 Mapper.xml 配置:
<mapper namespace="yh.dao.IStudentMapper">
<select id="selectStudents2" resultMap="StudentResultMap2">
select s.id sid,s.name sname,t.id tid,t.name tname
from mybatis.student s
join mybatis.teacher t on s.tid=t.id
</select>
<!--通過上面查詢的結果表進行處理-->
<resultMap id="StudentResultMap2" type="yh.pojo.Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<!--這個屬性為物件,所以進行復雜處理,在子標籤中對該物件做相對錶的對映-->
<association property="teacher" javaType="yh.pojo.Teacher">
<!--注意:結果表就相當於對應的資料庫表,column元素的值為結果表的欄位-->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
</mapper>
需要清楚的一個概念:
java 程式處理資料庫查詢時,是相對於執行 sql 查詢結果所產生結果表,而不是資料庫中實際的表。根據結果表中的欄位(如起別名,則結果表對應欄位就為該別名),mybatis 才能進行相關的對映。
測試程式碼:
@Test
public void selectStudent2(){
SqlSession session = MybatisUtils.getSqlSession();
IStudentMapper mapperS = session.getMapper(IStudentMapper.class);
List<Student> students = mapperS.selectStudents2();
for (Student student : students) {
System.out.println(student.toString());
}
session.close();
}
結果:
在此說明:程式是相對於查詢結果產生的表進行對映的。如果都沒有查詢某個欄位,那麼結果表中自然沒有,對應的 JavaBean 例項也不能賦值。
如上例,查詢語句為:select s.id sid,s.name sname,t.name tname
(查詢老師 id 的引數去掉了),那麼結果如下:
老師的 id 屬性獲取不到了(因為查詢結果表中沒有)。
一對多
如一個老師教了多個學生,對於老師來說就是一對多的關係。
那麼如何通過一個老師,去查詢它對應學生的資訊呢?
前言
一對多情況下兩個 JavaBean 的屬性設定:
public class Teacher {
private int id;
private String name;
/**
* 一個老師擁有多個學生,一對多
*/
private List<Student> students;
//略...
}
public class Student {
private int id;
private String name;
private int tid;
//略...
}
與多對一類似,同樣的兩種方式:
方式一:巢狀查詢
從 sql 查詢角度看,實際採用的子查詢方式,通過指定老師的編號去匹配學生資訊。
老師介面的 Mapper.xml 中的配置:
<mapper namespace="yh.dao.ITeacherMapper">
<select id="selectTeacherById2" resultMap="TeacherResultMap2">
select * from mybatis.teacher where id=#{id}
</select>
<resultMap id="TeacherResultMap2" type="Teacher">
<id property="id" column="id"/>
<collection property="students" javaType="ArrayList" ofType="Students" column="id" select="selectStudents"/>
</resultMap>
<select id="selectStudents" resultType="Student">
select *
from mybatis.student
where tid=#{id}
</select>
</mapper>
在查詢老師資訊的結果集 resultMap
元素中對映屬性複雜型別(集合)時,再進行查詢操作(巢狀),最終實現一對多查詢。
方式二:按結果巢狀查詢
使用連線查詢獲取一個帶有對應學生資訊結果表,從而實現對映處理。
老師介面中定義的方法:
/**
* 查詢指定id的老師資訊,以及其學生的資訊
* @param id
* @return
*/
Teacher selectTeacherById(@Param("id") int id);
老師介面的 Mapper.xml 中的配置
<mapper namespace="yh.dao.ITeacherMapper">
<select id="selectTeacherById" parameterType="int" resultMap="TeacherResultMap">
select t.id tid,t.name tname,s.id sid,s.name sname,s.tid tid
from mybatis.teacher t
inner join mybatis.student s
on t.id=s.tid
where t.id=#{id}
</select>
<resultMap id="TeacherResultMap" type="Teacher">
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--複雜的屬性我們需要單獨處理 物件:association 集合:collection
javaType:用於指定所指類屬性的型別
ofType:用於指定類的集合屬性中的泛型型別
-->
<collection property="students" ofType="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="tid" column="tid"/>
</collection>
</resultMap>
</mapper>
一對多相對於多對一,用的集合,集合屬性的對映處理需要用到 collection
元素,且指定集合泛型型別使用 ofType
屬性,其他基本相同。
測試:
@Test
public void selectTeacher2(){
SqlSession session = MybatisUtils.getSqlSession();
ITeacherMapper mapper = session.getMapper(ITeacherMapper.class);
Teacher teacher = mapper.selectTeacherById(1);
System.out.println(teacher.toString());
session.close();
}
結果:
小結
-
resultMap 結果對映
- 常用屬性
id
屬性:當前名稱空間中的一個唯一標識,用於標識一個結果對映。type
屬性:類的完全限定名, 或者一個型別別名
id
和result
元素的屬性:property
column
javaType
association
:關聯 - 【多對一】- javaType:用於指定所指類屬性的型別(全類名或型別別名)
collection
:集合 -【一對多】- 用於指定類的集合屬性中的泛型所用型別(全類名或型別別名)
慢 SQL:執行效率很低的 sql 語句。
相關 MySQL 內容:MySQL 引擎、InnoDB 底層原理、索引及其優化
- 常用屬性
六、動態 SQL
根據不同的條件,生成不同的 SQL 語句,在編譯時無法確定,只有等程式執行起來,執行過程中才能確定的 SQL語句為動態 SQL(在 SQL 層面執行一個邏輯程式碼)
資料表如下:
if
常用於根據判斷條件包含 where 子句的一部分,條件成立,被包含的部分得以執行,反之不執行。如:
<!--使用if實現動態查詢-->
<select id="getBlog" 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>
根據是否傳入引數控制是否增加篩選條件,如下:
@Test
public void test2(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Map<String, String> map = new HashMap<>();
//控制引數的有無,實現動態SQL效果
// map.put("title","Java如此簡單");
map.put("author","熊大");
List<Blog> blogs = mapper.getBlog(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
//未提交事務是因為在MybatisUtils工具類中開啟了自動提交
session.close();
}
choose、when、otherwise
choose
when
otherwise
類似於 java 中的 switch
case
,otherwise
,不同的是這裡判斷條件設定在 when
元素中,符合其條件的,就執行其所包含的 sql 語句。如下:
<!--使用choose、when、otherwise實現動態查詢-->
<select id="findActiveBlogLike" parameterType="Map" resultType="Blog">
select *
from mybatis.blog
where 1=1
<choose>
<when test="title != null">
and title like #{title}
</when>
<when test="author != null">
and author like #{author}
</when>
<otherwise>
title=#{title}
</otherwise>
</choose>
</select>
傳入的 title
就按照 title
的查詢,傳入了 author
就按 author
查詢,兩者都沒有就用 otherwise
元素中的,如兩個元素都傳入了,那個 when 元素先執行就用哪個。如此例就是執行 title
的查詢,測試程式碼如下:
@Test
public void test3(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Map<String, String> map = new HashMap<>();
//傳入兩個引數,也只執行先執行的那個
map.put("title","Java%");
map.put("author","熊%");
List<Blog> blogs = mapper.findActiveBlogLike(map);
for (Blog blog : blogs) {
System.out.println(blog.toString());
}
//未提交事務是因為在MybatisUtils工具類中開啟了自動提交
session.close();
}
trim、where、set
where 元素只會在子元素返回任何內容的情況下(有符合條件的子句時)才插入 “WHERE” 子句。且,若子句的開頭為 “AND” 或 “OR”,where 元素也會將它們去除。
<select id="findActiveBlogLike2" parameterType="Map" resultType="Blog">
select *
from mybatis.blog
# 被包含的子元素成立的情況下,插入where以及子元素包含的條件,且去除and字首
<where>
<if test="title != null">
and title like #{title}
</if>
<if test="author != null">
and author like #{author}
</if>
</where>
</select>
set 元素用於動態包含需要更新的列(條件成立的列),忽略其它不更新的列(條件不成立的列)。在首行插入 set
關鍵字,並會刪掉額外的逗號(最後一個更新欄位不要逗號),如下:
<update id="updateBlog" parameterType="Map">
update mybatis.blog
# 在首行插入 `set` 關鍵字,並會刪掉額外的逗號
<set>
<if test="id != null">id=#{id},</if>
<if test="author != null">author=#{author},</if>
<if test="createTime != null">create_time=#{createTime},</if>
<if test="views != null">views=#{views}</if>
</set>
where title=#{title}
</update>
測試程式碼:
@Test
public void test5() {
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
map.put("title","Java如此簡單");
map.put("author","熊大");
map.put("views","10000");
map.put("createTime",new Date());
int i = mapper.updateBlog(map);
if (i > 0) {
System.out.println("修改成功");
}
}
trim 元素用於自定義 where、set 的功能:
prefix
屬性:用於覆蓋的字首
prefixOverride
屬性:被覆蓋的字首
與上面用到的 where
等價的自定義 trim
元素:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
與上面用到的 set
等價的自定義 trim
元素:
<trim prefix="SET" suffixOverrides=",">
...
</trim>
注意:我們覆蓋了字尾值設定,並且自定義了字首值。
SQL 片段
將 SQL 語句中一些功能的部分抽取出出來,方便複用
-
使用
sql
標籤抽取公共部分,如:<sql id="if-title-author"> <if test="title != null"> title = #{title} </if> <if test="author != null"> and author = #{author} </if> </sql>
-
在需要使用的地方使用
Include
標籤引用即可:<select id="queryBlogIF" parameterType="Map" resyltType="Blog"> select * from mybatis.blog <where> <include refid="if-title-author"></include> </where> </select>
注意:1.最好基於表單來定義 SQL 片段;
2.片段中不要存在 where 標籤;
foreach
用於指定一個集合進行遍歷或指定開頭與結尾的字串以及集合項迭代之間的分隔符
coolection
屬性:指示傳遞過來用於遍歷的集合
item
屬性:集合當前遍歷出來的元素
index
屬性:索引(當前迭代的序號)
open
屬性:指定開始符號
close
屬性:指定結束符號
separator
屬性:指定分隔符
元素內包裹的是通過遍歷集合引數,之間用分隔符拼接,兩頭拼接開始結束符,說白了就是一個 sql 語句拼接的過程,拼接的 sql 長度,取決於所傳遞的集合長度。示例如下:
Mapper.xml:
<!--foreach-->
<select id="findBlogForeach" parameterType="Map" resultType="Blog">
select *
from mybatis.blog
<where>
<foreach collection="ids" item="id" open="(" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
測試程式碼:
@Test
public void test6(){
SqlSession session = MybatisUtils.getSqlSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Map<String, Object> map = new HashMap<>();
List<Integer> ids = new ArrayList<>();
//要查詢哪一條主句,就將它的id放進集合中,由foreach遍歷,拼接成sql語句,實現動態SQL效果
ids.add(1);
ids.add(3);
map.put("ids",ids);
List<Blog> blogForeach = mapper.findBlogForeach(map);
for (Blog foreach : blogForeach) {
System.out.println(foreach.toString());
}
session.close();
}
測試結果:
注意:
我們表中的
id
欄位是 varchar 型別的,而我們向集合中新增的資料是Integer
型別,但是也能作為判斷條件,原因是: MySQL 會進行隱式型別轉換(TypeHandler),但是需要注意,有些資料庫不支援隱式轉換,需要手動轉換; 前面說了動態 SQL 實際就是一個拼接 SQL 的過程,我們只需按照 SQL 的格式,去排列組合就可以了,所以必要的一些空格也需要留意(新版本的 mybatis 貌似已經幫我們留意了)。
快取
簡介
- 什麼是快取?
- 存在記憶體中的臨時資料
- 將使用者經常查詢的資料放在快取(記憶體)中,使用者查詢資料就不用去磁碟(關係型資料庫)上查詢,而直接從快取中查詢,從而提高查詢效率,提升高併發系統效能。
- 為什麼使用快取?
- 提升讀取資料的速度的同時,減少和資料庫的互動次數,減少系統開銷,提升了效率。
- 什麼樣的資料能使用快取
- 經常查詢並且不經常改變的資料。目的就是提高效率,如果資料經常變化還要去設定快取,適得其反。
Mybatis 快取
- MyBatis 中預設定義了:一級快取和二級快取
- 一級快取:
SqlSession
級別的快取(也稱為本地快取),即從獲取 SqlSession 到 SqlSession 被回收期間的快取。預設情況下,只有有一級快取開啟。 - 二級快取:基於 namespace 級別的快取,即當前 Mapper.xml 範圍的快取,需要手動開啟配置。
- MyBatis 中定義了快取介面
Cache
,可以通過實現Cache
介面來自定義二級快取。
- 一級快取:
一級快取
與資料庫同一次會話(SqlSession)期間查詢到的資料會放在本地快取中;再需要獲取相同資料時,直接從快取中拿。測試如下:
-
在 mybatisConfig.xml 中開啟日誌,方便觀察執行過程
<!-- 配置設定--> <settings> <!--標準的日誌工程實現--> <setting name="logImpl" value="STDOUT_LOGGING"/> <!-- log4j實現--> <!-- <setting name="logImpl" value="LOG4J"/>--> </settings>
-
介面中定義方法
/** * 根據id查使用者 * @param id * @return */ User findUserById(@Param("id") Integer id);
-
Mapper.xml 配置
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="yh.dao.UserMapper"> <select id="findUserById" parameterType="int" resultType="User"> select * from mybatis.user where id=#{id} </select> </mapper>
-
測試程式碼
@Test public void test1(){ SqlSession session = MybatisUtils.getSqlSession(); UserMapper mapper = session.getMapper(UserMapper.class); //同一次會話(SqlSession)中,第一次執行查詢 User user1 = mapper.findUserById(2); System.out.println("user1" + user1.toString()); //同一次會話(SqlSession)中,第二次執行查詢 User user2 = mapper.findUserById(2); System.out.println("user2" + user2.toString()); //比較查詢獲取的JavaBean例項地址是否相同(是否是同一個物件) System.out.println(user1 == user2); session.close(); }
-
對結果日誌進行分析
從程式執行看,第二次呼叫查詢時,沒有與資料庫進行互動,而兩次查詢所獲的 JavaBean 物件例項地址比較結果為
true
,所以可斷定第二次查詢的資料不是從資料庫獲取的,而是從本地快取(記憶體)獲取的,這也就是一級快取的作用。
- 快取失效的情況:
- 查詢不同的資料
- 進行了增刪改操作,快取會重新整理(因為原來的資料可能發生了改變)
- 手動清理快取:
SqlSession.clearCache();
一級快取預設是開啟的,只在一次 SQLSession 中有效,也就是獲取連線到關閉連線期間;一級快取就是一個 Map(key 記錄 sql,value 記錄對應的查詢結果)。
二級快取
二級快取也稱為全域性快取,一級快取作用域太低了(基於 SqlSession
級別),所以誕生了二級快取(基於 namespace
級別的快取),整個 Mapper ,對應一個二級快取;
- 工作機制
- 一個會話查詢一條資料,這個資料就會被放在當前會話的一級快取中;如果當前會話關閉了,這個會話的一級快取就沒了;如開啟了二級快取,會話關閉時,一級快取中的資料會被儲存到二級快取中;
- 新的會話查詢資訊就可以從二級快取中獲取內容;
- 不同
mapper
查出的資料會放在自己對應的快取中(同樣是用Map
儲存,key
為mapper
標識,value
為其二級快取資料)。
-
開啟全域性快取需要對 Mybatis 核心配置檔案的
settings
元素中進行如下配置 :<!--顯示地開啟全域性快取--> <setting name="cacheEnabled" value="true"/>
-
在要使用二級快取的 SQL 對映(Mapper.xml)中新增一個標籤:
<cache/>
也可以在其中自定義一些引數:
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
-
測試程式碼
-
結果分析
給 mapper 開啟二級快取前查詢結果可以清楚地看出兩次從與資料庫互動的過程:
而開啟了 mapper 快取後:
只與資料庫進行了一次互動,但是通過新增
<cache/>
標籤的方式查詢時,兩個查詢物件的比較結果確是false
,因為使用無參<cache/>
標籤時,未序列化就會報物件序列化異常,而序列化後對通過序列碼比價物件肯定是不同的,所以結果為false
。而使用自定義<cache/>
標籤屬性時,結果為true
:規範:實體類定義時需要實現序列化介面。
快取查詢順序
使用者進行查詢,先先查詢二級快取中是否有對應快取,有 返回查詢結果,無 再去一級快取查詢,有 返回結果,無 去資料庫查詢;
在查詢到資料返回結果時,存在一個快取過程(將資料存入記憶體),從資料庫查詢會根據當前開啟的快取級別,將資料存入級別高的快取中;在一級快取中查詢到結果時(說明在二級快取中沒有查到),返回資料時,一級快取會將資料快取進二級快取,最後返回結果。
一級快取提交前提是當前會話關閉,否則不會將快取送入二級快取。
流程圖:
如果第一次會話的快取沒有提交,則第一次會話中查詢的快取都不能從二級快取中查詢出來,只有第一次快取提交後,後續的會話查詢才能使用二級快取的結果。
在 mapper 中可通過設定 select
標籤的 useCache
屬性確定是否需要快取 ;CUD 標籤則通過 flush
屬性指定執行後是否重新整理快取。
自定義快取與 EhCache
EhCache 是一種廣泛使用的開源 Java 分散式快取,主要面向通用快取。
匯入依賴使用,在 mapper 中指定我們使用的 ehcache 快取實現:
<!--在當前Mapper.xml中使用耳機快取-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="./tmpdir/Tmp_EhCache"/>
<defaultCache
eternal="false"
maxElementsInMemory="10000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="259200"
memoryStoreEvictionPolicy="LRU"/>
<cache
name="cloud_user"
eternal="false"
maxElementsInMemory="5000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="1800"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>