文章目錄
- 前言
- 一、搭建MyBatis
- 1.1 創建maven工程
- 1.2 加入log4j日志功能
- 1.3 MyBatis的增刪改查
- 1.4 核心配置文件詳解
- 二、MyBatis獲取參數值的兩種方式
- 2.1 單個字面量類型的參數
- 2.2 多個字面量類型的參數
- 2.3 map集合類型的參數
- 2.4 實體類類型的參數
- 2.5 使用@Param標識參數
- 三、 MyBatis的各種查詢功能
- 3.1 查詢一個實體類對象
- 3.2 查詢一個list集合
- 3.3 查詢一條數據為map集合
- 3.4 查詢多條數據為map集合
- 四、 特殊SQL的執行
- 4.1 模糊查詢
- 4.2 批量刪除
- 4.3 動態設置表名
- 4.4 添加功能獲取自增的主鍵
- 五、 自定義映射resultMap
- 5.1 resultMap處理字段和屬性的映射關系
- 5.2 多對一映射處理
- 5.2.1 級聯方式處理映射關系
- 5.2.2 使用association處理映射關系(處理實體類類型的屬性映射)
- 5.2.3 分步查詢
- 分布查詢優點
- 5.3 一對多映射處理
- 5.3.1 collection
- 5.3.2 分步查詢
- 六、 動態SQL
- 6.1 if
- 6.2 where
- 6.3 trim
- 6.4 choose、when、otherwise
- 6.5 foreach
- 6.6 SQL片段
- 七、 MyBatis的緩存
- 7.1 MyBatis的一級緩存
- 7.2 MyBatis的二級緩存
- 7.3 二級緩存的相關配置
- 7.4 MyBatis緩存查詢的順序
- 7.5 整合第三方緩存EHCache
- 7.5.1、添加依賴
- 7.5.2 創建EHCache的配置文件ehcache.xml
- 7.5.3 設置二級緩存的類型
- 7.5.4 加入logback日志
- 八、 Mybatis逆向工程
- 8.1 創建逆向工程的步驟
- 九、 分頁查詢
- 分頁插件的使用步驟
- 分頁插件的使用
前言
- MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優秀的持久層框架
- MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集
- MyBatis可以使用簡單的XML或注解用于配置和原始映射,將接口和Java的POJO(Plain Old Java Objects,普通的Java對象)映射成數據庫中的記錄
- MyBatis 是一個 半自動的ORM(Object Relation Mapping)框架
提示:以下是本篇文章正文內容,下面案例可供參考
一、搭建MyBatis
MySQL不同版本的注意事項
1、驅動類driver-class-name
MySQL 5版本使用jdbc5驅動,驅動類使用:com.mysql.jdbc.Driver
MySQL 8版本使用jdbc8驅動,驅動類使用:com.mysql.cj.jdbc.Driver
2、連接地址url
MySQL 5版本的url:
jdbc:mysql://localhost:3306/ssm
MySQL 8版本的url:
jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC
否則運行測試用例報告如下錯誤:
java.sql.SQLException: The server time zone value ‘?D1ú±ê×?ê±??’ is unrecognized or represents more
1.1 創建maven工程
- 引入依賴
<dependencies><!-- Mybatis核心 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency><!-- junit測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- MySQL驅動 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency></dependencies>
- 創建MyBatis的核心配置文件
習慣上命名為mybatis-config.xml,這個文件名僅僅只是建議,并非強制要求。將來整合Spring
之后,這個配置文件可以省略,所以大家操作時可以直接復制、粘貼。
核心配置文件主要用于配置連接數據庫的環境以及MyBatis的全局配置信息
核心配置文件存放的位置是src/main/resources目錄下
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//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.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/ssm? serverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--引入映射文件--><mappers><package name="mappers/UserMapper.xml"/></mappers></configuration>
- 創建類
public class User {private Integer id;private String username;private String password;//有參構造public User(Integer id, String username, String password) {this.id = id;this.username = username;this.password = password;}public Integer id() {return id;}public void setId(Integer id) {this.id = id;}public String username() {return username;}public void setUsername(String username) {this.username = username;}public String password() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", password='" + password + '\'' +'}';}
}
- 創建mapper接口
MyBatis中的mapper接口相當于以前的dao。但是區別在于,mapper僅僅是接口,我們不需要提供實現類。
public interface UserMapper {/*** 添加用戶信息
*/int insertUser();
}
- 創建MyBatis的映射文件
相關概念:ORM(Object Relationship Mapping)對象關系映射。
對象:Java的實體類對象
關系:關系型數據庫
映射:二者之間的對應關系
Java概念 | 數據庫概念 |
---|---|
類 | 表 |
屬性 | 表的字段/列 |
對象 | 表中的一行數據 |
1、映射文件的命名規則:
表所對應的實體類的類名+Mapper.xml
例如:表t_user,映射的實體類為User,所對應的映射文件為UserMapper.xml
因此一個映射文件對應一個實體類,對應一張表的操作
MyBatis映射文件用于編寫SQL,訪問以及操作表中的數據
MyBatis映射文件存放的位置是src/main/resources/mappers目錄下
2、 MyBatis中可以面向接口操作數據,要保證兩個一致:
a> mapper接口的全類名和映射文件的命名空間(namespace)保持一致
b> mapper接口中方法的方法名和映射文件中編寫SQL的標簽的id屬性保持一
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.atguigu.mybatis.mapper.UserMapper"><!--對應映射接口:int insertUser();--><insert id="insertUser">insert into t_user values(null,'admin','123456')</insert></mapper>
6 . 通過junit測試功能
//讀取MyBatis的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");//創建SqlSessionFactoryBuilder對象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = newSqlSessionFactoryBuilder();//通過核心配置文件所對應的字節輸入流創建工廠類SqlSessionFactory,生產SqlSession對象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);//創建SqlSession對象,此時通過SqlSession對象所操作的sql都必須手動提交或回滾事務
//SqlSession sqlSession = sqlSessionFactory.openSession();//創建SqlSession對象,此時通過SqlSession對象所操作的sql都會自動提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);//通過代理模式創建UserMapper接口的代理實現類對象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);//調用UserMapper接口中的方法,就可以根據UserMapper的全類名匹配元素文件,通過調用的方法名匹配
映射文件中的SQL標簽,并執行標簽中的SQL語句
int result = userMapper.insertUser();//sqlSession.commit();System.out.println("結果:"+result)
- SqlSession:代表Java程序和數據庫之間的會話。(HttpSession是Java程序和瀏覽器之間的會話)
- SqlSessionFactory:是“生產”SqlSession的“工廠”。
- 工廠模式:如果創建某一個對象,使用的過程基本固定,那么我們就可以把創建這個對象的相關代碼封裝到一個“工廠類”中,以后都使用這個工廠類來“生產”我們需要的對象。
1.2 加入log4j日志功能
- 加入maven依賴
<!-- log4j日志 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
- 加入log4j的配置文件
log4j的配置文件名為log4j.xml,存放的位置是src/main/resources目錄下
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE log4j:configuration SYSTEM "log4j.dtd"><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><appender name="STDOUT" class="org.apache.log4j.ConsoleAppender"><param name="Encoding" value="UTF-8" /><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /></layout></appender><logger name="java.sql"><level value="debug" /></logger><logger name="org.apache.ibatis"><level value="info" /></logger><root><level value="debug" /><appender-ref ref="STDOUT" /></root>
</log4j:configuration>
日志的級別
FATAL(致命)>ERROR(錯誤)>WARN(警告)>INFO(信息)>DEBUG(調試)
從左到右打印的內容越來越詳細
1.3 MyBatis的增刪改查
對一個表的操作放在一個xml中,UserMapper.xml
- 新增
<!--int insertUser();-->
<insert id="insertUser"> insert into t_user values(null,'admin','123456')
</insert>
- 刪除
<!--int deleteUser();-->
<delete id="deleteUser"> delete from t_user where id = 7
</delete>
- 修改
<!--int updateUser();-->
<update id="updateUser"> update t_user set username='ybc',password='123' where id = 6
</update>
- 查詢(比較特殊)
注意:
1、查詢的標簽select必須設置屬性resultType或resultMap,用于設置實體類和數據庫表的映射關系
resultType:自動映射,用于屬性名和表中字段名一致的情況
resultMap:自定義映射,用于一對多或多對一或字段名和屬性名不一致的情況
這兩個屬性的值都是類的全類名com.xxx.xxx
查詢一個實體類對象
<!--User getUserById();-->
<select id="getUserById" resultType="com.atguigu.mybatis.bean.User"> select * from t_user where id = 2
</select>
查詢多個,返回一個對象集合
<!--對應的映射文件的接口:List<User> getUserList();-->
<select id="getUserList" resultType="com.atguigu.mybatis.bean.User"> select * from t_user
</select>
1.4 核心配置文件詳解
核心配置文件中的標簽必須按照固定的順序:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!--MyBatis核心配置文件中,標簽的順序:properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?--><!--引入properties文件,此后就可以在當前文件中使用${key}的方式訪問value,比如下文的dataSource標簽中--><properties resource="jdbc.properties" /><!--設置類型別名,在myBatis的范圍中,就可以使用別名表示一個具體的類型--><typeAliases><!--typeAlias:設置某個類型的別名屬性:type:設置需要設置別名的類型alias:設置某個類型的別名,若不設置該屬性,那么該類型擁有默認的別名,即類名且不區分大小寫--><!--<typeAlias type="com.atguigu.mybatis.pojo.User" alias="abc"></typeAlias>--><!--全范圍內可以用User代替com.atguigu.mybatis.pojo.User--><!--<typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>--><!--以包為單位,將包下所有的類型設置默認的類型別名,即類名且不區分大小寫,當類多時可以用這個方式--><package name="com.atguigu.mybatis.pojo"/></typeAliases><!--environments:配置多個連接數據庫的環境屬性:default:設置默認使用的環境的id--><environments default="development"><environment id="development"><!--transactionManager:設置事務管理方式屬性: type="JDBC|MANAGED"1. JDBC:表示當前環境中,執行SQL時,使用的是JDBC中原生的事務管理方式,事務的提交或回滾需要手動處理2. MANAGED:被管理,例如Spring--><transactionManager type="JDBC"/><!--dataSource:配置數據源屬性:type:設置數據源的類型type="POOLED|UNPOOLED|JNDI"POOLED:表示使用數據庫連接池緩存數據庫連接UNPOOLED:表示不使用數據庫連接池JNDI:表示使用上下文中的數據源--><dataSource type="POOLED"><!--設置連接數據庫的驅動--><property name="driver" value="${jdbc.driver}"/><!--設置連接數據庫的連接地址--><property name="url" value="${jdbc.url}"/><!--設置連接數據庫的用戶名--><property name="username" value="${jdbc.username}"/><!--設置連接數據庫的密碼--><property name="password" value="${jdbc.password}"/></dataSource></environment><environment id="test"><transactionManager type="JDBC"/><dataSource type="POOLED"><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/ssmserverTimezone=UTC"/><property name="username" value="root"/><property name="password" value="123456"/></dataSource></environment></environments><!--引入映射文件--><mappers><!--<mapper resource="mappers/UserMapper.xml"/>--><!--以包為單位引入映射文件要求:1、mapper接口所在的包要和映射文件所在的包一致,即resources下映射文件所在目錄必須和mapper接口所在目錄保持一致性2、mapper接口要和映射文件的名字一致--><package name="com.atguigu.mybatis.mapper"/></mappers></configuration>
二、MyBatis獲取參數值的兩種方式
MyBatis獲取參數值的兩種方式:${}和#{}
${}的本質就是字符串拼接,#{}的本質就是占位符賦值
使用
$
的方式進行傳參,相當于直接把參數拼接到原始的SQL里面,MyBatis不會對它進行特殊處理。若為字符串類型或日期類型的字段進行賦值時,需要手動加單引
#
號占位符,等同于jdbc里面的?
號占位符,它相當于向PreparedStatement中的預處理語句中設置參數,而PreparedStatement中的sql語句是預編譯的,SQL語句中使用了占位符規定了SQL語句的機構,并且在設置參數的時候,如果有特殊字符,會自動進行轉義,所以使用#號占位符還可以防止SQL注入。
<!-- User getUserByUsername(String username); -->
<select id="getUserByUsername" resultType="User"><!-- 通過${}占位符 獲取參數需要加單引號-->select * from user where username = '${username}';<!-- 通過#{}占位符 獲取參數填充,這里#{xxx}的xxx可以是任意名稱,因為底層這就是一個占位符 -->select * from user where username = #{username};
</select>
$和#最大的區別在于,前者是動態參數,后者是占位符,動態參數無法防止SQL注入的問題,所以在實際應用中,應該盡可能的使用#號占位符
另外,$占位符,可以應用在一些動態SQL場景中,比如動態傳遞表名,批量刪除等
2.1 單個字面量類型的參數
若mapper接口中的方法參數為單個的字面量類型
此時可以使用${}和#{}以任意的名稱獲取參數的值,注意${}需要手動加單引號
2.2 多個字面量類型的參數
若mapper接口中的方法參數為多個時此時MyBatis會自動將這些參數放在一個map集合中,以arg0,arg1…為鍵,以參數為值;以param1,param2…為鍵,以參數為值;(則會兩種都可以,只不過一個從0開始,一個從1開始)
因此只需要通過${}和#{}訪問map集合的鍵就可以獲取相對應的值,注意對于字符串或日期類型,${}需要手動加單引號
2.3 map集合類型的參數
若mapper接口中的方法需要的參數為多個時,此時可以手動創建map集合,將這些數據放在map中
只需要通過${}和#{}訪問map集合的鍵就可以獲取相對應的值,注意${}需要手動加單引號
2.4 實體類類型的參數
若mapper接口中的方法參數為實體類對象時 此時可以使用${}和#{},通過訪問實體類對象中的屬性名獲取屬性值,注意${}需要手動加單引號
注意,屬性名和成員變量沒有關系,只和get和set方法有關
2.5 使用@Param標識參數
可以通過@Param注解標識mapper接口中的方法參數
此時,會將這些參數放在map集合中,
1.以@Param注解的value屬性值為鍵,以參數為值;
2.以param1,param2…為鍵,以參數為值;
只需要通過${}和#{}訪問map集合的鍵就可以獲取相對應的值,
注意${}需要手動加單引號
mapper接口內容
映射文件內容,此時${}中的內容與@param注解中的屬性值一 一對應
測試內容
三、 MyBatis的各種查詢功能
3.1 查詢一個實體類對象
/*** 根據用戶id查詢用戶信息
* @param id* @return
*/User getUserById(@Param("id") int id);
<!--User getUserById(@Param("id") int id);-->
<select id="getUserById" resultType="User">select * from t_user where id = #{id}
</select>
3.2 查詢一個list集合
/*** 查詢所有用戶信息,因為是所有,所以不用加參數
* @return*/List<User> getUserList();
<!--List<User> getUserList();-->
<select id="getUserList" resultType="User">select * from t_user
</select>
3.3 查詢一條數據為map集合
/*** 根據用戶id查詢用戶信息為map集合
* @param id* @return
*/Map<String, Object> getUserToMap(@Param("id") int id);
<!--Map<String, Object> getUserToMap(@Param("id") int id);-->
<!--結果: {password=123456, sex=男 , id=1, age=23, username=admin}-->
<select id="getUserToMap" resultType="map">select * from t_user where id = #{id}
</select>
3.4 查詢多條數據為map集合
①方式一 (將查詢到的map添加到外層list中)
/**
* 查詢所有用戶信息為map集合
* @return
* 將表中的數據以map集合的方式查詢,一條數據對應一個map;
* 若有多條數據,就會產生多個map集合,此時可以將這些map放在一個list集合中獲取
*/List<Map<String, Object>> getAllUserToMap();
<!--Map<String, Object> getAllUserToMap();-->
<select id="getAllUserToMap" resultType="map">select * from t_user
</select>
②方式二 (將查詢到的map添加到外層map中)
/**
* 查詢所有用戶信息為map集合
* @return
* 將表中的數據以map的方式查詢,一條數據對應一個map;若有多條數據,就會產生多個map,并
且最終要以一個map集合的方式返回數據,此時需要通過@MapKey注解設置map集合的鍵,值是每條數據所對應的map
*/
// 以查詢出來的id值為外層map的鍵,每條內層map為值@MapKey("id")Map<String, Object> getAllUserToMap();
<!--Map<String, Object> getAllUserToMap();--><!--
{ 1={password=123456, sex=男, id=1, age=23, username=admin}, 2={password=123456, sex=男, id=2, age=23, username=張三}, 3={password=123456, sex=男, id=3, age=23, username=張三}
}--><select id="getAllUserToMap" resultType="map">select * from t_user</select>
四、 特殊SQL的執行
4.1 模糊查詢
/*** 測試模糊查詢
* @param mohu* @return
*/
List<User> testMohu(@Param("mohu") String mohu);
<!--List<User> testMohu(@Param("mohu") String mohu);--><select id="testMohu" resultType="User"><!--select * from t_user where username like '%${mohu}%'--><!--select * from t_user where username like concat('%',#{mohu},'%')-->select * from t_user where username like "%"#{mohu}"%"</select>
4.2 批量刪除
/*** 批量刪除
* @param ids* @return
*/int deleteMore(@Param("ids") String ids);
<!--int deleteMore(@Param("ids") String ids);,ids的格式是x,x,x(用逗號隔開)--><delete id="deleteMore"><!--不能用#{ids},因為#會自動給ids加單引號,delete from t_user where id in ('x,x,x')是錯誤的語法-->delete from t_user where id in (${ids})</delete>
4.3 動態設置表名
/*** 動態設置表名,查詢所有的用戶信息
* @param tableName* @return
*/List<User> getAllUser(@Param("tableName") String tableName);
<!--List<User> getAllUser(@Param("tableName") String tableName);-->
<select id="getAllUser" resultType="User"><!--同樣不能用#{}--> select * from ${tableName}
</select>
4.4 添加功能獲取自增的主鍵
場景模擬:
t_clazz(clazz_id,clazz_name)
t_student(student_id,student_name,clazz_id)
1、添加班級信息
2、獲取新添加的班級的id
3、為班級分配學生,即將某學的班級id修改為新添加的班級的id
/*** 添加用戶信息
* @param user* @return* useGeneratedKeys:設置使用自增的主鍵
* keyProperty:因為增刪改有統一的返回值是受影響的行數,因此只能將獲取的自增的主鍵放在傳輸的參
數user對象的某個屬性中
*/int insertUser(User user);
<!--int insertUser(User user);--><!--useGeneratedKeys:設置使用自增的主鍵keyProperty:將添加的數據的自增主鍵為實體類中的參數進行賦值--><insert id="insertUser" useGeneratedKeys="true" keyProperty="id">insert into t_user values(null,#{username},#{password},#{age},#{sex})</insert>
五、 自定義映射resultMap
5.1 resultMap處理字段和屬性的映射關系
若字段名和實體類中的屬性名不一致(比如命名不同,個數不同),則可以通過resultMap設置自定義映射
<!--resultMap:設置自定義映射屬性:id:表示自定義映射的唯一標識type:查詢的數據要映射的實體類的類型子標簽:id:設置主鍵的映射關系result:設置普通字段的映射關系association:設置多對一的映射關系collection:設置一對多的映射關系屬性:property:設置映射關系中實體類中的屬性名column:設置映射關系中表中的字段名
--><resultMap id="userMap" type="User"><id property="id" column="id"></id><result property="userName" column="user_name"></result><result property="password" column="password"></result><result property="age" column="age"></result><result property="sex" column="sex"></result></resultMap><!--List<User> testMohu(@Param("mohu") String mohu);--><select id="testMohu" resultMap="userMap"><!--select * from t_user where username like '%${mohu}%'-->select id,user_name,password,age,sex from t_user where user_name like concat('%',#{mohu},'%')</select>
若字段名和實體類中的屬性名不一致,但是字段名符合數據庫的規則(使用_),實體類中的屬性名符合Java的規則(使用駝峰)
此時也可通過以下兩種方式處理字段名和實體類中的屬性的映射關系
a> 可以通過為字段起別名的方式,保證和實體類中的屬性名保持一致
b> 可以在MyBatis的核心配置文件中設置一個全局配置信息mapUnderscoreToCamelCase,可以在查詢表中數據時,自動將_類型的字段名轉換為駝峰
例如:字段名user_name,設置了mapUnderscoreToCamelCase,此時字段名就會轉換為
userName
<settings><!--將下劃線映射為駝峰--><setting name="mapUnderscoreTocamelcase" value="true"/></settins>
5.2 多對一映射處理
場景模擬:查詢員工信息以及員工所對應的部門信息
一個員工對應一條部門信息,所以一個員工對應一個部門實體
一各部門對應多個員工信息,所以一個部門對應一個員工集合
員工類里面有屬性:private Dept dept; // Dept類中包含id,dept_name等部門屬性
對應了部門信息,此時搜索出來的sql信息中包含的部門信息不能和dept產生直接映射(因為屬性不一樣)
應該和dept中的屬性進行映射,此時就需要自定義映射了
有以下方法進行處理
5.2.1 級聯方式處理映射關系
<resultMap id="empDeptMap" type="Emp"> <id column="eid" property="eid"></id> <result column="ename" property="ename"></result><result column="age" property="age"></result><result column="sex" property="sex"></result><!--級聯映射--><result column="did" property="dept.did"></result><result column="dname" property="dept.dname"></result>
</resultMap><!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}
</select>
5.2.2 使用association處理映射關系(處理實體類類型的屬性映射)
<resultMap id="empDeptMap" type="Emp"><id column="eid" property="eid"></id><result column="ename" property="ename"></result><result column="age" property="age"></result><result column="sex" property="sex"></result><!--property是實體類中屬性名,javaType是該屬性的全類名,這里已經提前設置過全類名為別名Dept了,在1.4中有提到--><association property="dept" javaType="Dept"><id column="did" property="did"></id><result column="dname" property="dname"></result></association></resultMap><!--Emp getEmpAndDeptByEid(@Param("eid") int eid);--><select id="getEmpAndDeptByEid" resultMap="empDeptMap">select emp.*,dept.* from t_emp emp left join t_dept dept on emp.did = dept.did where emp.eid = #{eid}
</select>
5.2.3 分步查詢
①查詢員工信息
/*** 通過分步查詢查詢員工信息
* @param eid* @return
*/Emp getEmpByStep(@Param("eid") int eid);
<resultMap id="empDeptStepMap" type="Emp"><id column="eid" property="eid"></id><result column="ename" property="ename"></result><result column="age" property="age"></result><result column="sex" property="sex"></result><!--select:設置分步查詢,查詢某個屬性的值的 sql 的全類名.方法名(namespace.sqlId) column:將sql以及查詢結果中的某個字段 設置為分步查詢的條件,也就是相當于參數傳遞給下一個sql語句--><association property="dept" select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep" column="did"> </association>
</resultMap><!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">select * from t_emp where eid = #{eid}
</select>
②根據員工所對應的部門id查詢部門信息
/*** 分步查詢的第二步: 根據員工所對應的did查詢部門信息
* @param did* @return Dept
*/Dept getEmpDeptByStep(@Param("did") int did)
<!--Dept getEmpDeptByStep(@Param("did") int did);--><select id="getEmpDeptByStep" resultType="Dept">select * from t_dept where did = #{did}</select>
以上,就是
第一步:select * from t_emp where eid = #{eid}按員工id查詢到員工信息,
第二步:將員工信息中的部門id作為參數傳給 select * from t_dept where did = #{did},
第三步:將第二步查詢到的結果返回給association中的property=“dept”
分布查詢優點
分步查詢的優點:可以實現延遲加載
但是必須在核心配置文件中設置全局配置信息:
lazyLoadingEnabled
:延遲加載的全局開關。當開啟時,所有關聯對象都會延遲加載
aggressiveLazyLoading
:當開啟時,任何方法的調用都會加載該對象的所有屬性。否則,每個屬性會按需加載
此時就可以實現按需加載,獲取的數據是什么,就只會執行相應的sql。
此時可通過association
和collection中
的fetchType
屬性設置當前的分步查詢是否使用延遲加載,fetchType=“lazy(延遲加載)|eager(立即加載)”
5.3 一對多映射處理
部門對應多個員工,所以部門類中有一個員工集合的屬性List< Emp>
5.3.1 collection
/*** 根據部門id查新部門以及部門中的員工信息
* @param did* @return
*/Dept getDeptEmpByDid(@Param("did") int did);
<!--type是全類名--><resultMap id="deptEmpMap" type="Dept"><id property="did" column="did"></id><result property="dname" column="dname"></result><!--ofType:設置collection標簽所處理的集合屬性中 存儲數據的類型 --><collection property="emps" ofType="Emp"><id property="eid" column="eid"></id><result property="ename" column="ename"></result><result property="age" column="age"></result><result property="sex" column="sex"></result></collection></resultMap><!--Dept getDeptEmpByDid(@Param("did") int did);--><select id="getDeptEmpByDid" resultMap="deptEmpMap">select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did = emp.did where dept.did = #{did}</select>
5.3.2 分步查詢
①查詢部門信息
/*** 分步查詢部門和部門中的員工
* @param did* @return
*/Dept getDeptByStep(@Param("did") int did);
<resultMap id="deptEmpStep" type="Dept"><id property="did" column="did"></id><result property="dname" column="dname"></result><!--fetchType:延遲查詢select:設置分步查詢,查詢某個屬性的值的 sql 的全類名.方法名(namespace.sqlId) column:將sql以及查詢結果中的某個字段 設置為分步查詢的條件,也就是相當于參數傳遞給下一個sql語句--><collection property="emps" fetchType="eager"select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid" column="did"></collection>
</resultMap><!--Dept getDeptByStep(@Param("did") int did);--><select id="getDeptByStep" resultMap="deptEmpStep">select * from t_dept where did = #{did}</select>
②根據部門id查詢部門中的所有員工
/*** 根據部門id查詢員工信息
* @param did* @return List<Emp>*/List<Emp> getEmpListByDid(@Param("did") int did);
<!--將查詢道德結果賦值給第一步中的emps屬性--><!--List<Emp> getEmpListByDid(@Param("did") int did);--><select id="getEmpListByDid" resultType="Emp">select * from t_emp where did = #{did}</select>
執行順序查考5.2.3中的順序
六、 動態SQL
Mybatis框架的動態SQL技術是一種根據特定條件動態拼裝SQL語句的功能,它存在的意義是為了解決 拼接SQL語句字符串時的痛點問題
6.1 if
if標簽可通過
test
屬性的表達式進行判斷,若表達式的結果為true,則標簽中的內容會執行;反之標簽中的內容不會執行
<!--List<Emp> getEmpListByCondition(Emp emp);--><select id="getEmpListByMoreTJ" resultType="Emp">select * from t_emp where 1=1 <if test="ename != '' and ename != null"> and ename = #{ename} </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="sex != '' and sex != null"> and sex = #{sex} </if>
</select>
6.2 where
where和if一般結合使用:
a> 若where標簽中的if條件都不滿足,則where標簽沒有任何功能,即不會添加where關鍵字
b> 若where標簽中的if條件滿足,則where標簽會自動添加where關鍵字,并將條件最前方多余的and去掉
注意:where標簽不能去掉條件最后多余的and
<select id="getEmpListByMoreTJ2" resultType="Emp"> select * from t_emp <where> <if test="ename != '' and ename != null"> ename = #{ename} </if> <if test="age != '' and age != null"> and age = #{age} </if> <if test="sex != '' and sex != null"> and sex = #{sex} </if> </where>
</select>
6.3 trim
trim用于去掉或添加標簽中的內容
常用屬性:
prefix:在trim標簽中的內容的前面添加某些內容
prefixOverrides:在trim標簽中的內容的前面去掉某些內容
suffix:在trim標簽中的內容的后面添加某些內容
suffixOverrides:在trim標簽中的內容的后面去掉某些內容
<select id="getEmpListByMoreTJ" resultType="Emp">select * from t_emp<trim prefix="where" suffixOverrides="and"><if test="ename != '' and ename != null">ename = #{ename} and</if><if test="age != '' and age != null">age = #{age} and</if><if test="sex != '' and sex != null">sex = #{sex}</if></trim></select>
6.4 choose、when、otherwise
choose、when、 otherwise相當于if…else if…else
<!--List<Emp> getEmpListByChoose(Emp emp);--><select id="getEmpListByChoose" resultType="Emp">select <include refid="empColumns"></include> from t_emp<where><choose><when test="ename != '' and ename != null">ename = #{ename}</when><when test="age != '' and age != null">age = #{age}</when><when test="sex != '' and sex != null">sex = #{sex}</when><when test="email != '' and email != null">email = #{email}</when></choose></where></select>
6.5 foreach
<!--int insertMoreEmp(List<Emp> emps);--><insert id="insertMoreEmp">insert into t_emp values<foreach collection="emps" item="emp" separator=",">(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)</foreach></insert><!--int deleteMoreByArray(int[] eids);--><delete id="deleteMoreByArray">delete from t_emp where<foreach collection="eids" item="eid" separator="or">eid = #{eid}</foreach></delete><!--int deleteMoreByArray(int[] eids);--><delete id="deleteMoreByArray">delete from t_emp where eid in<foreach collection="eids" item="eid" separator="," open="(" close=")"> #{eid}</foreach>
</delete>
6.6 SQL片段
sql片段,可以記錄一段公共sql片段,在使用的地方通過include標簽進行引入,在映射
<sql id="empColumns">eid,ename,age,sex,did
</sql>select <include refid="empColumns"></include> from t_emp
七、 MyBatis的緩存
SqlSession 是 MyBatis 的核心接口之一,用于執行與數據庫的交互操作。它提供了執行 SQL 語句的所有方法,包括插入、更新、刪除和查詢,還可以管理事務、獲取映射器(Mapper)接口的實例等。
SqlSession 的主要功能包括:
執行SQL操作:如 insert、update、delete、select 等方法,用于執行對應的 SQL 語句。
事務管理:通過 commit() 和 rollback() 方法進行事務的提交與回滾。
獲取Mapper:通過 getMapper(Class type) 方法獲取映射器接口的實例,從而使用接口調用來執行 SQL。
緩存管理:SqlSession 還負責管理一級緩存,它會自動緩存相同會話中的查詢結果,減少對數據庫的訪問。
7.1 MyBatis的一級緩存
一級緩存是SqlSession級別的,通過同一個SqlSession查詢的數據會被緩存,下次查詢相同的數據,就會從緩存中直接獲取,不會從數據庫重新訪問
使一級緩存失效的四種情況:
- 不同的SqlSession對應不同的一級緩存
- 同一個SqlSession但是查詢條件不同
- 同一個SqlSession兩次查詢期間執行了任何一次增刪改操作
- 同一個SqlSession兩次查詢期間手動清空了緩存
7.2 MyBatis的二級緩存
二級緩存是SqlSessionFactory級別,通過同一個SqlSessionFactory創建的SqlSession查詢的結果會被緩存;此后若再次執行相同的查詢語句,結果就會從緩存中獲取
二級緩存開啟的條件:
a> 在核心配置文件中,設置全局配置屬性cacheEnabled=“true”,默認為true,不需要設置
b> 在映射文件中設置標簽< cache/>
c> 二級緩存必須在SqlSession關閉或提交之后有效,關閉或提交之前,是保存在了一級緩存中,SqlSession關閉之后,一級緩存中的數據會寫入二級緩存
d> 查詢的數據所轉換的實體類類型必須實現序列化的接口
使二級緩存失效的情況:
兩次查詢之間執行了任意的增刪改,會使一級和二級緩存同時失效
7.3 二級緩存的相關配置
在mapper配置文件中添加的cache標簽可以設置一些屬性:
①eviction屬性:緩存回收策略,默認的是 LRU。
- LRU(Least Recently Used) 最近最少使用的:移除最長時間不被使用的對象。
- FIFO(First in First out) – 先進先出:按對象進入緩存的順序來移除它們。
- SOFT – 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。
- WEAK –弱引用:更積極地移除基于垃圾收集器狀態和弱引用規則的對象。
②flushInterval屬性:刷新間隔,單位毫秒
默認情況是不設置,也就是沒有刷新間隔,緩存僅僅調用語句時刷新
③size屬性:引用數目,正整數
代表緩存最多可以存儲多少個對象,太大容易導致內存溢出
④readOnly屬性:只讀, true/false
true:只讀緩存;會給所有調用者返回緩存對象的相同實例。因此這些對象不能被修改。這提供了 很重要的性能優勢。
false:讀寫緩存;會返回緩存對象的拷貝(通過序列化)。這會慢一些,但是安全,因此默認是false。
7.4 MyBatis緩存查詢的順序
先查詢二級緩存,因為二級緩存中可能會有其他程序已經查出來的數據,可以拿來直接使用。
如果二級緩存沒有命中,再查詢一級緩存
如果一級緩存也沒有命中,則查詢數據庫
SqlSession關閉之后,一級緩存中的數據會寫入二級緩存
7.5 整合第三方緩存EHCache
7.5.1、添加依賴
<!-- Mybatis EHCache整合包 --><dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-ehcache</artifactId><version>1.2.1</version></dependency><!-- slf4j日志門面的一個具體實現 --><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency>
7.5.2 創建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="utf-8" ?><ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"><!-- 磁盤保存路徑 --><diskStore path="D:\atguigu\ehcache"/><defaultCachemaxElementsInMemory="1000"maxElementsOnDisk="10000000"eternal="false"overflowToDisk="true"timeToIdleSeconds="120"timeToLiveSeconds="120"diskExpiryThreadIntervalSeconds="120"memoryStoreEvictionPolicy="LRU"></defaultCache></ehcache>
7.5.3 設置二級緩存的類型
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
7.5.4 加入logback日志
存在SLF4J時,作為簡易日志的log4j將失效,此時我們需要借助SLF4J的具體實現logback來打印日志。 創建logback的配置文件logback.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug="true"><!-- 指定日志輸出的位置 --><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><!-- 日志輸出的格式 --><!-- 按照順序分別是: 時間、日志級別、線程名稱、打印日志的類、日志主體內容、換行 --><pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern></encoder></appender><!-- 設置全局日志級別。日志級別按順序分別是: DEBUG、INFO、WARN、ERROR --> <!-- 指定任何一個日志級別都只打印當前級別和后面級別的日志。 --><root level="DEBUG"><!-- 指定打印日志的appender,這里通過“STDOUT”引用了前面配置的appender --><appender-ref ref="STDOUT" /></root><!-- 根據特殊需求指定局部日志級別 --><logger name="com.atguigu.crowd.mapper" level="DEBUG"/></configuration>
八、 Mybatis逆向工程
正向工程:先創建Java實體類,由框架負責根據實體類生成數據庫表。 Hibernate是支持正向工程的。
逆向工程:先創建數據庫表,由框架負責根據數據庫表,反向生成如下資源:
- Java實體類
- Mapper接口
- Mapper映射文件
8.1 創建逆向工程的步驟
①添加依賴和插件
<!-- 依賴MyBatis核心包 -->
<dependencies><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.7</version></dependency><!-- junit測試 --><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!-- log4j日志 --><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency></dependencies><!-- 控制Maven在構建過程中相關配置 -->
<build><!-- 構建過程中用到的插件 --><plugins><!-- 具體插件,逆向工程的操作是以構建過程中插件形式出現的 --><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.0</version><!-- 插件的依賴 --><dependencies><!-- 逆向工程的核心依賴 --><dependency><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-core</artifactId><version>1.3.2</version></dependency><!-- MySQL驅動 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency></dependencies></plugin></plugins>
</build>
②創建MyBatis的核心配置文件
③創建逆向工程的配置文件
在resources目錄下,文件名必須是:generatorConfig.xml
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"><generatorConfiguration><!--targetRuntime: 執行生成的逆向工程的版本<1>MyBatis3Simple: 生成基本的CRUD(清新簡潔版)<2>MyBatis3: 生成帶條件的CRUD(奢華尊享版)--><context id="DB2Tables" targetRuntime="MyBatis3"><!-- 數據庫的連接信息 --><jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"connectionURL="jdbc:mysql://localhost:3306/mybatis? serverTimezone=UTC"userId="root"password="123456"></jdbcConnection><!-- javaBean的生成策略--><javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java"><property name="enableSubPackages" value="true" /><property name="trimStrings" value="true" /></javaModelGenerator><!-- SQL映射文件的生成策略 --><sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\resources"><property name="enableSubPackages" value="true" /></sqlMapGenerator><!-- Mapper接口的生成策略 --><javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper" targetProject=".\src\main\java"><property name="enableSubPackages" value="true" /></javaClientGenerator><!-- 逆向分析的表 --><!-- tableName設置為*號,可以對應所有表,此時不寫domainObjectName --><!-- domainObjectName屬性指定生成出來的實體類的類名 --><table tableName="t_emp" domainObjectName="Emp"/><table tableName="t_dept" domainObjectName="Dept"/></context></generatorConfiguration>
④執行MBG插件的generate目標
⑤效果
九、 分頁查詢
為了更高效和便捷地實現分頁,MyBatis 社區開發了多種分頁插件。這些插件通過攔截器(Interceptor)機制,在 SQL 執行前或執行后自動修改 SQL 語句或處理查詢結果,實現數據庫層面的分頁。
分頁插件的工作原理主要包括以下步驟:
- 攔截器機制:分頁插件通過 MyBatis 的攔截器機制攔截執行 SQL 的方法,例如
Executor
接口中的query
方法。通過攔截器,插件可以在 SQL 執行之前或執行之后對 SQL 語句進行修改。 - 自動添加分頁語句:當攔截到查詢方法時,分頁插件會檢測傳入的參數是否包含分頁信息(如
pageNum
和pageSize
)。如果包含,插件會根據數據庫類型自動為原始 SQL 語句添加相應的分頁語句(如LIMIT
、OFFSET
、ROWNUM
等)。 - 執行分頁 SQL:經過插件修改的 SQL 會被執行器執行,數據庫返回分頁后的結果集。
- 封裝結果:插件可以進一步封裝查詢結果,將其封裝為分頁對象,如
Page<T>
,以便開發者方便地使用分頁結果。
PageHelper:PageHelper 是 MyBatis 中最流行的分頁插件。它通過自動攔截查詢語句,添加
LIMIT
和OFFSET
子句來實現分頁,并且能夠自動處理分頁參數和結果集封裝。
- 優點
- 自動化:分頁插件通過攔截和修改 SQL 語句,實現了自動分頁,開發者無需手動編寫分頁 SQL。
- 高效:插件直接在數據庫層面實現分頁,避免了內存分頁的性能瓶頸。
- 易用性:插件通常會封裝分頁結果,使得開發者可以方便地處理分頁數據,如獲取總記錄數、總頁數、當前頁等信息。
- 缺點
- 復雜性:分頁插件增加了系統的復雜性,尤其是在處理復雜查詢或特定數據庫時,可能需要額外的配置或調試。
- 依賴性:使用分頁插件時,系統對插件產生了依賴性,如果插件更新或不再維護,可能會影響系統的穩定性。
- 擴展性:有時插件可能不支持某些高級或自定義的分頁需求,開發者需要自己擴展或修改插件代碼。
分頁插件的使用步驟
①添加依賴
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.2.0</version>
</dependency>
②配置分頁插件
在MyBatis的核心配置文件中配置插件
<plugins><!--設置分頁插件--><plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
分頁插件的使用
a> 在查詢功能之前使用Page< object> page=PageHelper.startPage(int pageNum, int pageSize)開啟分頁功能
- pageNum:當前頁的頁碼
- pageSize:每頁顯示的條數
b> 在查詢獲取list集合之后,使用PageInfo< T> pageInfo = new PageInfo<>(List< T> list, int navigatePages)獲取分頁相關數
- list:分頁之后的數據
- navigatePages:導航分頁的頁碼數
c>分頁相關數據
PageInfo{
pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8,
list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30,
pages=8, reasonable=false, pageSizeZero=false},
prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true,
hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8,
navigatepageNums=[4, 5, 6, 7, 8]
}
pageNum:當前頁的頁碼
pageSize:每頁顯示的條數
size:當前頁顯示的真實條數
total:總記錄數
pages:總頁數
prePage:上一頁的頁碼
nextPage:下一頁的頁碼
isFirstPage/isLastPage:是否為第一頁/最后一頁
hasPreviousPage/hasNextPage:是否存在上一頁/下一頁
navigatePages:導航分頁的頁碼數
navigatepageNums:導航分頁的頁碼,[1,2,3,4,5]