前言
B站Up狂神說的教程,寫的挺好的,東西也新。強烈推薦
https://www.bilibili.com/read/cv5702420
微信文檔
狂神說MyBatis01:第一個程序:https://mp.weixin.qq.com/s/vy-TUFa1Rb69ekxiEYGRqw
狂神說MyBatis02:CRUD操作及配置解析:https://mp.weixin.qq.com/s/efqEupNSPZUKqrRoGAnxzQ
狂神說MyBatis03:ResultMap及分頁:https://mp.weixin.qq.com/s/LXXIR7W_kZBHPIDqkNwrLg
狂神說MyBatis04:使用注解開發:https://mp.weixin.qq.com/s/3x3ynp5qhGNP-leeOxBU6g
狂神說MyBatis05:一對多和多對一處理:https://mp.weixin.qq.com/s/Dpt0LHTgx0kL7RCk8PZ2aw
狂神說MyBatis06:動態SQL:https://mp.weixin.qq.com/s/gtXsTG2naMTDnC6aqQICMg
狂神說MyBatis07:緩存:https://mp.weixin.qq.com/s/MwkfQNMXD6oO3dOaQYDdGA
Mybatis簡介
環境說明:
- jdk 8 +
- MySQL 5.7.19
- maven-3.6.1
- IDEA
學習前需要掌握:
- JDBC
- MySQL
- Java 基礎
- Maven
- Junit
什么是MyBatis
- MyBatis 是一款優秀的持久層框架
- MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集的過程
- MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 實體類 【Plain Old Java Objects,普通的 Java對象】映射成數據庫中的記錄。
- MyBatis 本是apache的一個開源項目ibatis, 2010年這個項目由apache 遷移到了google code,並且改名為MyBatis 。
- 2013年11月遷移到Github .
- Mybatis官方文檔 : http://www.mybatis.org/mybatis-3/zh/index.html
- GitHub : https://github.com/mybatis/mybatis-3
持久化
持久化是將程序數據在持久狀態和瞬時狀態間轉換的機制。
- 即把數據(如內存中的對象)保存到可永久保存的存儲設備中(如磁盤)。持久化的主要應用是將內存中的對象存儲在數據庫中,或者存儲在磁盤文件中、XML數據文件中等等。
- JDBC就是一種持久化機制。文件IO也是一種持久化機制。
- 在生活中 : 將鮮肉冷藏,吃的時候再解凍的方法也是。將水果做成罐頭的方法也是。
為什么需要持久化服務呢?那是由於內存本身的缺陷引起的
- 內存斷電后數據會丟失,但有一些對象是無論如何都不能丟失的,比如銀行賬號等,遺憾的是,人們還無法保證內存永不掉電。
- 內存過於昂貴,與硬盤、光盤等外存相比,內存的價格要高2~3個數量級,而且維持成本也高,至少需要一直供電吧。所以即使對象不需要永久保存,也會因為內存的容量限制不能一直呆在內存中,需要持久化來緩存到外存。
持久層
什么是持久層?
- 完成持久化工作的代碼塊 . ----> dao層 【DAO (Data Access Object) 數據訪問對象】
- 大多數情況下特別是企業級應用,數據持久化往往也就意味着將內存中的數據保存到磁盤上加以固化,而持久化的實現過程則大多通過各種關系數據庫來完成。
- 不過這里有一個字需要特別強調,也就是所謂的“層”。對於應用系統而言,數據持久功能大多是必不可少的組成部分。也就是說,我們的系統中,已經天然的具備了“持久層”概念?也許是,但也許實際情況並非如此。之所以要獨立出一個“持久層”的概念,而不是“持久模塊”,“持久單元”,也就意味着,我們的系統架構中,應該有一個相對獨立的邏輯層面,專注於數據持久化邏輯的實現.
- 與系統其他部分相對而言,這個層面應該具有一個較為清晰和嚴格的邏輯邊界。【說白了就是用來操作數據庫存在的!】
為什么需要Mybatis
-
Mybatis就是幫助程序猿將數據存入數據庫中 , 和從數據庫中取數據 .
-
傳統的jdbc操作 , 有很多重復代碼塊 .比如 : 數據取出時的封裝 , 數據庫的建立連接等等... , 通過框架可以減少重復代碼,提高開發效率 .
-
MyBatis 是一個半自動化的ORM框架 (Object Relationship Mapping) -->對象關系映射
-
所有的事情,不用Mybatis依舊可以做到,只是用了它,所有實現會更加簡單!技術沒有高低之分,只有使用這個技術的人有高低之別
-
MyBatis的優點
-
- 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件就可以了,易於學習,易於使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實現。
- 靈活:mybatis不會對應用程序或者數據庫的現有設計強加任何影響。sql寫在xml里,便於統一管理和優化。通過sql語句可以滿足操作數據庫的所有需求。
- 解除sql與程序代碼的耦合:通過提供DAO層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。
- 提供xml標簽,支持編寫動態sql。
- .......
-
最重要的一點,使用的人多!公司需要!
MyBatis第一個程序
思路流程:搭建環境-->導入Mybatis--->編寫代碼--->測試
代碼演示
1、搭建實驗數據庫
CREATE DATABASE `mybatis`;
USE `mybatis`;
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into `user`(`id`,`name`,`pwd`) values (1,'狂神','123456'),(2,'張三','abcdef'),(3,'李四','987654');
2、導入MyBatis相關 jar 包
- GitHub上找
<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>
3、編寫MyBatis核心配置文件
- 查看幫助文檔
<?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="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/kuang/dao/userMapper.xml"/>
</mappers>
</configuration>
4、編寫MyBatis工具類xml
- 查看幫助文檔
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;
public class MybatisUtils {
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();
}
}
//獲取SqlSession連接
public static SqlSession getSession(){
return sqlSessionFactory.openSession();
}
}
5、創建實體類
public class User {
private int id; //id
private String name; //姓名
private String pwd; //密碼
//構造,有參,無參
//set/get
//toString()
}
6、編寫Mapper接口類
import com.kuang.pojo.User;
import java.util.List;
public interface UserMapper {
List<User> selectUser();
}
7、編寫Mapper.xml配置文件
- namespace 十分重要,不能寫錯!
<?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="com.kuang.dao.UserMapper">
<select id="selectUser" resultType="com.kuang.pojo.User">
select * from user
</select>
</mapper>
8、編寫測試類
- Junit 包測試
public class MyTest {
@Test
public void selectUser() {
SqlSession session = MybatisUtils.getSession();
//方法一:
//List<User> users = session.selectList("com.kuang.mapper.UserMapper.selectUser");
//方法二:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user: users){
System.out.println(user);
}
session.close();
}
}
9、運行測試,成功的查詢出來的我們的數據,ok!
問題說明
可能出現問題說明:Maven靜態資源過濾問題
<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>
有了MyBatis以后再也不用寫原生的JDBC代碼了,舒服!
CRUD操作
namespace
- 將上面案例中的UserMapper接口改名為 UserDao;
- 將UserMapper.xml中的namespace改為為UserDao的路徑 .
- 再次測試
結論:
配置文件中namespace中的名稱為對應Mapper接口或者Dao接口的完整包名,必須一致!
select
-
select標簽是mybatis中最常用的標簽之一
-
select語句有很多屬性可以詳細配置每一條SQL語句
-
- SQL語句返回值類型。【完整的類名或者別名】
- 傳入SQL語句的參數類型 。【萬能的Map,可以多嘗試使用】
- 命名空間中唯一的標識符
- 接口中的方法名與映射文件中的SQL語句ID 一一對應
- id
- parameterType
- resultType
需求:根據id查詢用戶
1、在UserMapper中添加對應方法
public interface UserMapper {
//查詢全部用戶
List<User> selectUser();
//根據id查詢用戶
User selectUserById(int id);
}
2、在UserMapper.xml中添加Select語句
<select id="selectUserById" resultType="com.kuang.pojo.User">
select * from user where id = #{id}
</select>
3、測試類中測試
@Test
public void tsetSelectUserById() {
SqlSession session = MybatisUtils.getSession(); //獲取SqlSession連接
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
課堂練習:根據 密碼 和 名字 查詢用戶
思路一:直接在方法中傳遞參數
1、在接口方法的參數前加 @Param屬性
2、Sql語句編寫的時候,直接取@Param中設置的值即可,不需要單獨設置參數類型
//通過密碼和名字查詢用戶
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);
/*
<select id="selectUserByNP" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
</select>
*/
思路二:使用萬能的Mapx
1、在接口方法中,參數直接傳遞Map;
User selectUserByNP2(Map<String,Object> map);
2、編寫sql語句的時候,需要傳遞參數類型,參數類型為map
<select id="selectUserByNP2" parameterType="map" resultType="com.kuang.pojo.User">
select * from user where name = #{username} and pwd = #{pwd}
</select>
3、在使用方法的時候,Map的 key 為 sql中取的值即可,沒有順序要求!
Map<String, Object> map = new HashMap<String, Object>();
map.put("username","小明");
map.put("pwd","123456");
User user = mapper.selectUserByNP2(map);
總結:如果參數過多,我們可以考慮直接使用Map實現,如果參數比較少,直接傳遞參數即可
insert
我們一般使用insert標簽進行插入操作,它的配置和select標簽差不多!
需求:給數據庫增加一個用戶
1、在UserMapper接口中添加對應的方法
//添加一個用戶
int addUser(User user);
2、在UserMapper.xml中添加insert語句
<insert id="addUser" parameterType="com.kuang.pojo.User">
insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
</insert>
3、測試
@Test
public void testAddUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(5,"王五","zxcvbn");
int i = mapper.addUser(user);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
注意點:增、刪、改操作需要提交事務!
update
我們一般使用update標簽進行更新操作,它的配置和select標簽差不多!
需求:修改用戶的信息
1、同理,編寫接口方法
//修改一個用戶
int updateUser(User user);
2、編寫對應的配置文件SQL
<update id="updateUser" parameterType="com.kuang.pojo.User">
update user set name=#{name},pwd=#{pwd} where id = #{id}
</update>
3、測試
@Test
public void testUpdateUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
user.setPwd("asdfgh");
int i = mapper.updateUser(user);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
delete
我們一般使用delete標簽進行刪除操作,它的配置和select標簽差不多!
需求:根據id刪除一個用戶
1、同理,編寫接口方法
//根據id刪除用戶
int deleteUser(int id);
2、編寫對應的配置文件SQL
<delete id="deleteUser" parameterType="int">
delete from user where id = #{id}
</delete>
3、測試
@Test
public void testDeleteUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
int i = mapper.deleteUser(5);
System.out.println(i);
session.commit(); //提交事務,重點!不寫的話不會提交到數據庫
session.close();
}
小結:
- 所有的增刪改操作都需要提交事務!
- 接口所有的普通參數,盡量都寫上@Param參數,尤其是多個參數時,必須寫上!
- 有時候根據業務的需求,可以考慮使用map傳遞參數!
- 為了規范操作,在SQL的配置文件中,我們盡量將Parameter參數和resultType都寫上!
思考題
模糊查詢like語句該怎么寫?
第1種:在Java代碼中添加sql通配符。
string wildcardname = “%smi%”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like #{value}
</select>
第2種:在sql語句中拼接通配符,會引起sql注入
string wildcardname = “smi”;
list<name> names = mapper.selectlike(wildcardname);
<select id=”selectlike”>
select * from foo where bar like "%"#{value}"%"
</select>
配置解析
核心配置文件
- mybatis-config.xml 系統核心配置文件
- MyBatis 的配置文件包含了會深深影響 MyBatis 行為的設置和屬性信息。
- 能配置的內容如下:
configuration(配置)
properties(屬性)
settings(設置)
typeAliases(類型別名)
typeHandlers(類型處理器)
objectFactory(對象工廠)
plugins(插件)
environments(環境配置)
environment(環境變量)
transactionManager(事務管理器)
dataSource(數據源)
databaseIdProvider(數據庫廠商標識)
mappers(映射器)
<!-- 注意元素節點的順序!順序不對會報錯 -->
我們可以閱讀 mybatis-config.xml 上面的dtd的頭文件!
environments元素
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="..." value="..."/>
</transactionManager>
<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>
-
配置MyBatis的多套運行環境,將SQL映射到多個不同的數據庫上,必須指定其中一個為默認運行環境(通過default指定)
-
子元素節點:environment
-
-
dataSource 元素使用標准的 JDBC 數據源接口來配置 JDBC 連接對象的資源。
-
數據源是必須配置的。
-
有三種內建的數據源類型
type="[UNPOOLED|POOLED|JNDI]")
-
unpooled:這個數據源的實現只是每次被請求時打開和關閉連接。
-
pooled:這種數據源的實現利用“池”的概念將 JDBC 連接對象組織起來 , 這是一種使得並發 Web 應用快速響應請求的流行處理方式。
-
jndi:這個數據源的實現是為了能在如 Spring 或應用服務器這類容器中使用,容器可以集中或在外部配置數據源,然后放置一個 JNDI 上下文的引用。
-
數據源也有很多第三方的實現,比如dbcp,c3p0,druid等等....
-
詳情:點擊查看官方文檔
-
這兩種事務管理器類型都不需要設置任何屬性。
-
具體的一套環境,通過設置id進行區別,id保證唯一!
-
子元素節點:transactionManager - [ 事務管理器 ]
<!-- 語法 --> <transactionManager type="[ JDBC | MANAGED ]"/>
-
子元素節點:數據源(dataSource)
-
mappers元素
mappers
- 映射器 : 定義映射SQL語句文件
- 既然 MyBatis 的行為其他元素已經配置完了,我們現在就要定義 SQL 映射語句了。但是首先我們需要告訴 MyBatis 到哪里去找到這些語句。Java 在自動查找這方面沒有提供一個很好的方法,所以最佳的方式是告訴 MyBatis 到哪里去找映射文件。你可以使用相對於類路徑的資源引用, 或完全限定資源定位符(包括
file:///
的 URL),或類名和包名等。映射器是MyBatis中最核心的組件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL語句都必須在xml文件中配置。而從MyBatis 3開始,還支持接口映射器,這種映射器方式允許以Java代碼的方式注解定義SQL語句,非常簡潔。
引入資源方式
<!-- 使用相對於類路徑的資源引用 -->
<mappers>
<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定資源定位符(URL) -->
<mappers>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!--
使用映射器接口實現類的完全限定類名
需要配置文件名稱和接口名稱一致,並且位於同一目錄下
-->
<mappers>
<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!--
將包內的映射器接口實現全部注冊為映射器
但是需要配置文件名稱和接口名稱一致,並且位於同一目錄下
-->
<mappers>
<package name="org.mybatis.builder"/>
</mappers>
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="com.kuang.mapper.UserMapper">
</mapper>
-
namespace中文意思:命名空間,作用如下:
-
- namespace的命名必須跟某個接口同名
- 接口中的方法與映射文件中sql語句id應該一一對應
-
- namespace和子元素的id聯合保證唯一 , 區別不同的mapper
- 綁定DAO接口
- namespace命名規則 : 包名+類名
MyBatis 的真正強大在於它的映射語句,這是它的魔力所在。由於它的異常強大,映射器的 XML 文件就顯得相對簡單。如果拿它跟具有相同功能的 JDBC 代碼進行對比,你會立即發現省掉了將近 95% 的代碼。MyBatis 為聚焦於 SQL 而構建,以盡可能地為你減少麻煩。
Properties優化
數據庫這些屬性都是可外部配置且可動態替換的,既可以在典型的 Java 屬性文件中配置,亦可通過 properties 元素的子元素來傳遞。具體的官方文檔
我們來優化我們的配置文件
第一步 ; 在資源目錄下新建一個db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=123456
第二步 : 將文件導入properties 配置文件
<configuration>
<!--導入properties文件-->
<properties resource="db.properties"/>
<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="mapper/UserMapper.xml"/>
</mappers>
</configuration>
更多操作,可以查看官方文檔!【演示帶領學習】
- 配置文件優先級問題
- 新特性:使用占位符
typeAliases優化
類型別名是為 Java 類型設置一個短的名字。它只和 XML 配置有關,存在的意義僅在於用來減少類完全限定名的冗余。
<!--配置別名,注意順序-->
<typeAliases>
<typeAlias type="com.kuang.pojo.User" alias="User"/>
</typeAliases>
當這樣配置時,User
可以用在任何使用com.kuang.pojo.User
的地方。
也可以指定一個包名,MyBatis 會在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
<package name="com.kuang.pojo"/>
</typeAliases>
每一個在包 com.kuang.pojo
中的 Java Bean,在沒有注解的情況下,會使用 Bean 的首字母小寫的非限定類名來作為它的別名。
若有注解,則別名為其注解值。見下面的例子:
@Alias("user")
public class User {
...
}
去官網查看一下Mybatis默認的一些類型別名!
其他配置瀏覽
設置
-
設置(settings)相關 => 查看幫助文檔
-
- 懶加載
- 日志實現
- 緩存開啟關閉
-
一個配置完整的 settings 元素的示例如下:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
類型處理器
- 無論是 MyBatis 在預處理語句(PreparedStatement)中設置一個參數時,還是從結果集中取出一個值時, 都會用類型處理器將獲取的值以合適的方式轉換成 Java 類型。
- 你可以重寫類型處理器或創建你自己的類型處理器來處理不支持的或非標准的類型。【了解即可】
對象工廠
- MyBatis 每次創建結果對象的新實例時,它都會使用一個對象工廠(ObjectFactory)實例來完成。
- 默認的對象工廠需要做的僅僅是實例化目標類,要么通過默認構造方法,要么在參數映射存在的時候通過有參構造方法來實例化。
- 如果想覆蓋對象工廠的默認行為,則可以通過創建自己的對象工廠來實現。【了解即可】
生命周期和作用域
作用域(Scope)和生命周期
理解我們目前已經討論過的不同作用域和生命周期類是至關重要的,因為錯誤的使用會導致非常嚴重的並發問題。
我們可以先畫一個流程圖,分析一下Mybatis的執行過程!
作用域理解
- SqlSessionFactoryBuilder 的作用在於創建 SqlSessionFactory,創建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在於創建 SqlSessionFactory 的方法中,而不要讓其長期存在。因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域(也就是局部方法變量)。
- SqlSessionFactory 可以被認為是一個數據庫連接池,它的作用是創建 SqlSession 接口對象。因為 MyBatis 的本質就是 Java 對數據庫的操作,所以 SqlSessionFactory 的生命周期存在於整個 MyBatis 的應用之中,所以一旦創建了 SqlSessionFactory,就要長期保存它,直至不再使用 MyBatis 應用,所以可以認為 SqlSessionFactory 的生命周期就等同於 MyBatis 的應用周期。
- 由於 SqlSessionFactory 是一個對數據庫的連接池,所以它占據着數據庫的連接資源。如果創建多個 SqlSessionFactory,那么就存在多個數據庫連接池,這樣不利於對數據庫資源的控制,也會導致數據庫連接資源被消耗光,出現系統宕機等情況,所以盡量避免發生這樣的情況。
- 因此在一般的應用中我們往往希望 SqlSessionFactory 作為一個單例,讓它在應用中被共享。所以說 SqlSessionFactory 的最佳作用域是應用作用域。
- 如果說 SqlSessionFactory 相當於數據庫連接池,那么 SqlSession 就相當於一個數據庫連接(Connection 對象),你可以在一個事務里面執行多條 SQL,然后通過它的 commit、rollback 等方法,提交或者回滾事務。所以它應該存活在一個業務請求中,處理完整個請求后,應該關閉這條連接,讓它歸還給 SqlSessionFactory,否則數據庫資源就很快被耗費精光,系統就會癱瘓,所以用 try...catch...finally... 語句來保證其正確關閉。
- 所以 SqlSession 的最佳的作用域是請求或方法作用域。
學會了Crud,和基本的配置及原理,后面就可以學習些業務開發
ResultMap
查詢為null問題
要解決的問題:屬性名和字段名不一致
環境:新建一個項目,將之前的項目拷貝過來
1、查看之前的數據庫的字段名
2、Java中的實體類設計
public class User {
private int id; //id
private String name; //姓名
private String password; //密碼和數據庫不一樣!
//構造
//set/get
//toString()
}
3、接口
//根據id查詢用戶
User selectUserById(int id);
4、mapper映射文件
<select id="selectUserById" resultType="user">
select * from user where id = #{id}
</select>
5、測試
@Test
public void testSelectUserById() {
SqlSession session = MybatisUtils.getSession(); //獲取SqlSession連接
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
結果:
- User{id=1, name='狂神', password='null'}
- 查詢出來發現 password 為空 . 說明出現了問題!
分析:
-
select * from user where id = #{id} 可以看做
select id,name,pwd from user where id = #{id}
-
mybatis會根據這些查詢的列名(會將列名轉化為小寫,數據庫不區分大小寫) , 去對應的實體類中查找相應列名的set方法設值 , 由於找不到setPwd() , 所以password返回null ; 【自動映射】
解決方案
方案一:為列名指定別名 , 別名和java實體類的屬性名一致 .
<select id="selectUserById" resultType="User">
select id , name , pwd as password from user where id = #{id}
</select>
方案二:使用結果集映射->ResultMap 【推薦】
<resultMap id="UserMap" type="User">
<!-- id為主鍵 -->
<id column="id" property="id"/>
<!-- column是數據庫表的列名 , property是對應實體類的屬性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
ResultMap
自動映射
resultMap
元素是 MyBatis 中最重要最強大的元素。它可以讓你從 90% 的 JDBCResultSets
數據提取代碼中解放出來。- 實際上,在為一些比如連接的復雜語句編寫映射代碼的時候,一份
resultMap
能夠代替實現同等功能的長達數千行的代碼。 - ResultMap 的設計思想是,對於簡單的語句根本不需要配置顯式的結果映射,而對於復雜一點的語句只需要描述它們的關系就行了。
你已經見過簡單映射語句的示例了,但並沒有顯式指定 resultMap
。比如:
<select id="selectUserById" resultType="map">
select id , name , pwd
from user
where id = #{id}
</select>
上述語句只是簡單地將所有的列映射到 HashMap
的鍵上,這由 resultType
屬性指定。雖然在大部分情況下都夠用,但是 HashMap 不是一個很好的模型。你的程序更可能會使用 JavaBean 或 POJO(Plain Old Java Objects,普通老式 Java 對象)作為模型。
ResultMap
最優秀的地方在於,雖然你已經對它相當了解了,但是根本就不需要顯式地用到他們。
手動映射
1、返回值類型為resultMap
<select id="selectUserById" resultMap="UserMap">
select id , name , pwd from user where id = #{id}
</select>
2、編寫resultMap,實現手動映射!
<resultMap id="UserMap" type="User">
<!-- id為主鍵 -->
<id column="id" property="id"/>
<!-- column是數據庫表的列名 , property是對應實體類的屬性名 -->
<result column="name" property="name"/>
<result column="pwd" property="password"/>
</resultMap>
如果世界總是這么簡單就好了。但是肯定不是的,數據庫中,存在一對多,多對一的情況,我們之后會使用到一些高級的結果集映射,association,collection這些,我們將在之后講解,今天你們需要把這些知識都消化掉才是最重要的!理解結果集映射的這個概念!
分頁的幾種方式
日志工廠
思考:我們在測試SQL的時候,要是能夠在控制台輸出 SQL 的話,是不是就能夠有更快的排錯效率?
如果一個 數據庫相關的操作出現了問題,我們可以根據輸出的SQL語句快速排查問題。
對於以往的開發過程,我們會經常使用到debug模式來調節,跟蹤我們的代碼執行過程。但是現在使用Mybatis是基於接口,配置文件的源代碼執行過程。因此,我們必須選擇日志工具來作為我們開發,調節程序的工具。
Mybatis內置的日志工廠提供日志功能,具體的日志實現有以下幾種工具:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
具體選擇哪個日志實現工具由MyBatis的內置日志工廠確定。它會使用最先找到的(按上文列舉的順序查找)。如果一個都未找到,日志功能就會被禁用。
標准日志實現
指定 MyBatis 應該使用哪個日志記錄實現。如果此設置不存在,則會自動發現日志記錄實現。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
測試,可以看到控制台有大量的輸出!我們可以通過這些輸出來判斷程序到底哪里出了Bug
Log4j
簡介:
- Log4j是Apache的一個開源項目
- 通過使用Log4j,我們可以控制日志信息輸送的目的地:控制台,文本,GUI組件....
- 我們也可以控制每一條日志的輸出格式;
- 通過定義每一條日志信息的級別,我們能夠更加細致地控制日志的生成過程。最令人感興趣的就是,這些可以通過一個配置文件來靈活地進行配置,而不需要修改應用的代碼。
使用步驟:
1、導入log4j的包
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2、配置文件編寫
#將等級為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/kuang.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
3、setting設置日志實現
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
4、在程序中使用Log4j進行輸出!
//注意導包:org.apache.log4j.Logger
static Logger logger = Logger.getLogger(MyTest.class);
@Test
public void selectUser() {
logger.info("info:進入selectUser方法");
logger.debug("debug:進入selectUser方法");
logger.error("error: 進入selectUser方法");
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.selectUser();
for (User user: users){
System.out.println(user);
}
session.close();
}
5、測試,看控制台輸出!
- 使用Log4j 輸出日志
- 可以看到還生成了一個日志的文件 【需要修改file的日志級別】
limit實現分頁
思考:為什么需要分頁?
在學習mybatis等持久層框架的時候,會經常對數據進行增刪改查操作,使用最多的是對數據庫進行查詢操作,如果查詢大量數據的時候,我們往往使用分頁進行查詢,也就是每次處理小部分數據,這樣對數據庫壓力就在可控范圍內。
使用Limit實現分頁
#語法
SELECT * FROM table LIMIT stratIndex,pageSize
SELECT * FROM table LIMIT 5,10; // 檢索記錄行 6-15
#為了檢索從某一個偏移量到記錄集的結束所有的記錄行,可以指定第二個參數為 -1:
SELECT * FROM table LIMIT 95,-1; // 檢索記錄行 96-last.
#如果只給定一個參數,它表示返回最大的記錄行數目:
SELECT * FROM table LIMIT 5; //檢索前 5 個記錄行
#換句話說,LIMIT n 等價於 LIMIT 0,n。
步驟:
1、修改Mapper文件
<select id="selectUser" parameterType="map" resultType="user">
select * from user limit #{startIndex},#{pageSize}
</select>
2、Mapper接口,參數為map
//選擇全部用戶實現分頁
List<User> selectUser(Map<String,Integer> map);
3、在測試類中傳入參數測試
- 推斷:起始位置 = (當前頁面 - 1 ) * 頁面大小
//分頁查詢 , 兩個參數startIndex , pageSize
@Test
public void testSelectUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
int currentPage = 1; //第幾頁
int pageSize = 2; //每頁顯示幾個
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("startIndex",(currentPage-1)*pageSize);
map.put("pageSize",pageSize);
List<User> users = mapper.selectUser(map);
for (User user: users){
System.out.println(user);
}
session.close();
}
RowBounds分頁
我們除了使用Limit在SQL層面實現分頁,也可以使用RowBounds在Java代碼層面實現分頁,當然此種方式作為了解即可。我們來看下如何實現的!
步驟:
1、mapper接口
//選擇全部用戶RowBounds實現分頁
List<User> getUserByRowBounds();
2、mapper文件
<select id="getUserByRowBounds" resultType="user">
select * from user
</select>
3、測試類
在這里,我們需要使用RowBounds類
@Test
public void testUserByRowBounds() {
SqlSession session = MybatisUtils.getSession();
int currentPage = 2; //第幾頁
int pageSize = 2; //每頁顯示幾個
RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);
//通過session.**方法進行傳遞rowBounds,[此種方式現在已經不推薦使用了]
List<User> users = session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null, rowBounds);
for (User user: users){
System.out.println(user);
}
session.close();
}
PageHelper
了解即可,可以自己嘗試使用
官方文檔:https://pagehelper.github.io/
在MyBatisPlus中,我們也講解到了分頁實現,所以實現方式很多,看自己的理解和熟練程度進行掌握即可!
會了簡單的結果集映射,后面我們就可以實現一對多和多對一操作了
使用注解開發
上集回顧:狂神說MyBatis03:ResultMap及分頁
面向接口編程
- 大家之前都學過面向對象編程,也學習過接口,但在真正的開發中,很多時候我們會選擇面向接口編程
- 根本原因 : 解耦 , 可拓展 , 提高復用 , 分層開發中 , 上層不用管具體的實現 , 大家都遵守共同的標准 , 使得開發變得容易 , 規范性更好
- 在一個面向對象的系統中,系統的各種功能是由許許多多的不同對象協作完成的。在這種情況下,各個對象內部是如何實現自己的,對系統設計人員來講就不那么重要了;
- 而各個對象之間的協作關系則成為系統設計的關鍵。小到不同類之間的通信,大到各模塊之間的交互,在系統設計之初都是要着重考慮的,這也是系統設計的主要工作內容。面向接口編程就是指按照這種思想來編程。
關於接口的理解
-
接口從更深層次的理解,應是定義(規范,約束)與實現(名實分離的原則)的分離。
-
接口的本身反映了系統設計人員對系統的抽象理解。
-
接口應有兩類:
-
- 第一類是對一個個體的抽象,它可對應為一個抽象體(abstract class);
- 第二類是對一個個體某一方面的抽象,即形成一個抽象面(interface);
-
一個體有可能有多個抽象面。抽象體與抽象面是有區別的。
三個面向區別
-
面向對象是指,我們考慮問題時,以對象為單位,考慮它的屬性及方法 .
-
面向過程是指,我們考慮問題時,以一個具體的流程(事務過程)為單位,考慮它的實現 .
-
接口設計與非接口設計是針對復用技術而言的,與面向對象(過程)不是一個問題.更多的體現就是對系統整體的架構
利用注解開發
-
mybatis最初配置信息是基於 XML ,映射語句(SQL)也是定義在 XML 中的。而到MyBatis 3提供了新的基於注解的配置。不幸的是,Java 注解的的表達力和靈活性十分有限。最強大的 MyBatis 映射並不能用注解來構建
-
sql 類型主要分成 :
-
- @select ()
- @update ()
- @Insert ()
- @delete ()
注意:利用注解開發就不需要mapper.xml映射文件了 .
1、我們在我們的接口中添加注解
//查詢全部用戶
@Select("select id,name,pwd password from user")
public List<User> getAllUser();
2、在mybatis的核心配置文件中注入
<!--使用class綁定接口-->
<mappers>
<mapper class="com.kuang.mapper.UserMapper"/>
</mappers>
3、我們去進行測試
@Test
public void testGetAllUser() {
SqlSession session = MybatisUtils.getSession();
//本質上利用了jvm的動態代理機制
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> users = mapper.getAllUser();
for (User user : users){
System.out.println(user);
}
session.close();
}
4、利用Debug查看本質
5、本質上利用了jvm的動態代理機制
6、Mybatis詳細的執行流程
注解增刪改
改造MybatisUtils工具類的getSession( ) 方法,重載實現。
//獲取SqlSession連接
public static SqlSession getSession(){
return getSession(true); //事務自動提交
}
public static SqlSession getSession(boolean flag){
return sqlSessionFactory.openSession(flag);
}
【注意】確保實體類和數據庫字段對應
查詢:
1、編寫接口方法注解
//根據id查詢用戶
@Select("select * from user where id = #{id}")
User selectUserById(@Param("id") int id);
2、測試
@Test
public void testSelectUserById() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1);
System.out.println(user);
session.close();
}
新增:
1、編寫接口方法注解
//添加一個用戶
@Insert("insert into user (id,name,pwd) values (#{id},#{name},#{pwd})")
int addUser(User user);
2、測試
@Test
public void testAddUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(6, "秦疆", "123456");
mapper.addUser(user);
session.close();
}
修改:
1、編寫接口方法注解
//修改一個用戶
@Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
int updateUser(User user);
2、測試
@Test
public void testUpdateUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = new User(6, "秦疆", "zxcvbn");
mapper.updateUser(user);
session.close();
}
刪除:
1、編寫接口方法注解
//根據id刪除用
@Delete("delete from user where id = #{id}")
int deleteUser(@Param("id")int id);
2、測試
@Test
public void testDeleteUser() {
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
mapper.deleteUser(6);
session.close();
}
【注意點:增刪改一定記得對事務的處理】
關於@Param
@Param注解用於給方法參數起一個名字。以下是總結的使用原則:
- 在方法只接受一個參數的情況下,可以不使用@Param。
- 在方法接受多個參數的情況下,建議一定要使用@Param注解給參數命名。
- 如果參數是 JavaBean , 則不能使用@Param。
- 不使用@Param注解時,參數只能有一個,並且是Javabean。
#與$的區別
-
#{} 的作用主要是替換預編譯語句(PrepareStatement)中的占位符? 【推薦使用】
INSERT INTO user (name) VALUES (#{name}); INSERT INTO user (name) VALUES (?);
-
${} 的作用是直接進行字符串替換
INSERT INTO user (name) VALUES ('${name}'); INSERT INTO user (name) VALUES ('kuangshen');
使用注解和配置文件協同開發,才是MyBatis的最佳實踐!
多對一的處理
多對一的理解:
- 多個學生對應一個老師
- 如果對於學生這邊,就是一個多對一的現象,即從學生這邊關聯一個老師!
數據庫設計
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, '秦老師');
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', '小李', '1');
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');
搭建測試環境
1、IDEA安裝Lombok插件
2、引入Maven依賴
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
3、在代碼中增加注解
@Data //GET,SET,ToString,有參,無參構造
public class Teacher {
private int id;
private String name;
}
@Data
public class Student {
private int id;
private String name;
//多個學生可以是同一個老師,即多對一
private Teacher teacher;
}
4、編寫實體類對應的Mapper接口 【兩個】
- 無論有沒有需求,都應該寫上,以備后來之需!
public interface StudentMapper {
}
public interface TeacherMapper {
}
5、編寫Mapper接口對應的 mapper.xml配置文件 【兩個】
- 無論有沒有需求,都應該寫上,以備后來之需!
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.StudentMapper">
</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="com.kuang.mapper.TeacherMapper">
</mapper>
按查詢嵌套處理
1、給StudentMapper接口增加方法
//獲取所有學生及對應老師的信息
public List<Student> getStudents();
2、編寫對應的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="com.kuang.mapper.StudentMapper">
<!--
需求:獲取所有學生及對應老師的信息
思路:
1. 獲取所有學生的信息
2. 根據獲取的學生信息的老師ID->獲取該老師的信息
3. 思考問題,這樣學生的結果集中應該包含老師,該如何處理呢,數據庫中我們一般使用關聯查詢?
1. 做一個結果集映射:StudentTeacher
2. StudentTeacher結果集的類型為 Student
3. 學生中老師的屬性為teacher,對應數據庫中為tid。
多個 [1,...)學生關聯一個老師=> 一對一,一對多
4. 查看官網找到:association – 一個復雜類型的關聯;使用它來處理關聯查詢
-->
<select id="getStudents" resultMap="StudentTeacher">
select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
<!--association關聯屬性 property屬性名 javaType屬性類型 column在多的一方的表中的列名-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
這里傳遞過來的id,只有一個屬性的時候,下面可以寫任何值
association中column多參數配置:
column="{key=value,key=value}"
其實就是鍵值對的形式,key是傳給下個sql的取值名稱,value是片段一中sql查詢的字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id}
</select>
</mapper>
3、編寫完畢去Mybatis配置文件中,注冊Mapper!
4、注意點說明:
<resultMap id="StudentTeacher" type="Student">
<!--association關聯屬性 property屬性名 javaType屬性類型 column在多的一方的表中的列名-->
<association property="teacher" column="{id=tid,name=tid}" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!--
這里傳遞過來的id,只有一個屬性的時候,下面可以寫任何值
association中column多參數配置:
column="{key=value,key=value}"
其實就是鍵值對的形式,key是傳給下個sql的取值名稱,value是片段一中sql查詢的字段名。
-->
<select id="getTeacher" resultType="teacher">
select * from teacher where id = #{id} and name = #{name}
</select>
5、測試
@Test
public void testGetStudents(){
SqlSession session = MybatisUtils.getSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents();
for (Student student : students){
System.out.println(
"學生名:"+ student.getName()
+"\t老師:"+student.getTeacher().getName());
}
}
按結果嵌套處理
除了上面這種方式,還有其他思路嗎?
我們還可以按照結果進行嵌套處理;
1、接口方法編寫
public List<Student> getStudents2();
2、編寫對應的mapper文件
<!--
按查詢結果嵌套處理
思路:
1. 直接查詢出結果,進行結果集的映射
-->
<select id="getStudents2" resultMap="StudentTeacher2" >
select s.id sid, s.name sname , t.name tname
from student s,teacher t
where s.tid = t.id
</select>
<resultMap id="StudentTeacher2" type="Student">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<!--關聯對象property 關聯對象在Student實體類中的屬性-->
<association property="teacher" javaType="Teacher">
<result property="name" column="tname"/>
</association>
</resultMap>
3、去mybatis-config文件中注入【此處應該處理過了】
4、測試
@Test
public void testGetStudents2(){
SqlSession session = MybatisUtils.getSession();
StudentMapper mapper = session.getMapper(StudentMapper.class);
List<Student> students = mapper.getStudents2();
for (Student student : students){
System.out.println(
"學生名:"+ student.getName()
+"\t老師:"+student.getTeacher().getName());
}
}
小結
按照查詢進行嵌套處理就像SQL中的子查詢
按照結果進行嵌套處理就像SQL中的聯表查詢
一對多的處理
一對多的理解:
- 一個老師擁有多個學生
- 如果對於老師這邊,就是一個一對多的現象,即從一個老師下面擁有一群學生(集合)!
實體類編寫
@Data
public class Student {
private int id;
private String name;
private int tid;
}
@Data
public class Teacher {
private int id;
private String name;
//一個老師多個學生
private List<Student> students;
}
..... 和之前一樣,搭建測試的環境!
按結果嵌套處理
1、TeacherMapper接口編寫方法
//獲取指定老師,及老師下的所有學生
public Teacher getTeacher(int id);
2、編寫接口對應的Mapper配置文件
<mapper namespace="com.kuang.mapper.TeacherMapper">
<!--
思路:
1. 從學生表和老師表中查出學生id,學生姓名,老師姓名
2. 對查詢出來的操作做結果集映射
1. 集合的話,使用collection!
JavaType和ofType都是用來指定對象類型的
JavaType是用來指定pojo中屬性的類型
ofType指定的是映射到list集合屬性中pojo的類型。
-->
<select id="getTeacher" resultMap="TeacherStudent">
select s.id sid, s.name sname , t.name tname, t.id tid
from student s,teacher t
where s.tid = t.id and t.id=#{id}
</select>
<resultMap id="TeacherStudent" type="Teacher">
<result property="name" column="tname"/>
<collection property="students" ofType="Student">
<result property="id" column="sid" />
<result property="name" column="sname" />
<result property="tid" column="tid" />
</collection>
</resultMap>
</mapper>
3、將Mapper文件注冊到MyBatis-config文件中
<mappers>
<mapper resource="mapper/TeacherMapper.xml"/>
</mappers>
4、測試
@Test
public void testGetTeacher(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
按查詢嵌套處理
1、TeacherMapper接口編寫方法
public Teacher getTeacher2(int id);
2、編寫接口對應的Mapper配置文件
<select id="getTeacher2" resultMap="TeacherStudent2">
select * from teacher where id = #{id}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
<!--column是一對多的外鍵 , 寫的是一的主鍵的列名-->
<collection property="students" javaType="ArrayList" ofType="Student" column="id" select="getStudentByTeacherId"/>
</resultMap>
<select id="getStudentByTeacherId" resultType="Student">
select * from student where tid = #{id}
</select>
3、將Mapper文件注冊到MyBatis-config文件中
4、測試
@Test
public void testGetTeacher2(){
SqlSession session = MybatisUtils.getSession();
TeacherMapper mapper = session.getMapper(TeacherMapper.class);
Teacher teacher = mapper.getTeacher2(1);
System.out.println(teacher.getName());
System.out.println(teacher.getStudents());
}
小結
1、關聯-association
2、集合-collection
3、所以association是用於一對一和多對一,而collection是用於一對多的關系
4、JavaType和ofType都是用來指定對象類型的
- JavaType是用來指定pojo中屬性的類型
- ofType指定的是映射到list集合屬性中pojo的類型。
注意說明:
1、保證SQL的可讀性,盡量通俗易懂
2、根據實際要求,盡量編寫性能更高的SQL語句
3、注意屬性名和字段不一致的問題
4、注意一對多和多對一 中:字段和屬性對應的問題
5、盡量使用Log4j,通過日志來查看自己的錯誤
一對多和多對一對於很多人來說是難點,一定要大量的做練習理解!
動態SQL
介紹
什么是動態SQL:動態SQL指的是根據不同的查詢條件 , 生成不同的Sql語句.
官網描述:
MyBatis 的強大特性之一便是它的動態 SQL。如果你有使用 JDBC 或其它類似框架的經驗,你就能體會到根據不同條件拼接 SQL 語句的痛苦。例如拼接時要確保不能忘記添加必要的空格,還要注意去掉列表最后一個列名的逗號。利用動態 SQL 這一特性可以徹底擺脫這種痛苦。
雖然在以前使用動態 SQL 並非一件易事,但正是 MyBatis 提供了可以被用在任意 SQL 映射語句中的強大的動態 SQL 語言得以改進這種情形。
動態 SQL 元素和 JSTL 或基於類似 XML 的文本處理器相似。在 MyBatis 之前的版本中,有很多元素需要花時間了解。MyBatis 3 大大精簡了元素種類,現在只需學習原來一半的元素便可。MyBatis 采用功能強大的基於 OGNL 的表達式來淘汰其它大部分元素。
-------------------------------
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
-------------------------------
我們之前寫的 SQL 語句都比較簡單,如果有比較復雜的業務,我們需要寫復雜的 SQL 語句,往往需要拼接,而拼接 SQL ,稍微不注意,由於引號,空格等缺失可能都會導致錯誤。
那么怎么去解決這個問題呢?這就要使用 mybatis 動態SQL,通過 if, choose, when, otherwise, trim, where, set, foreach等標簽,可組合成非常靈活的SQL語句,從而在提高 SQL 語句的准確性的同時,也大大提高了開發人員的效率。
搭建環境
新建一個數據庫表:blog
字段:id,title,author,create_time,views
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
1、創建Mybatis基礎工程
2、IDutil工具類
public class IDUtil {
public static String genId(){
return UUID.randomUUID().toString().replaceAll("-","");
}
}
3、實體類編寫 【注意set方法作用】
import java.util.Date;
public class Blog {
private String id;
private String title;
private String author;
private Date createTime;
private int views;
//set,get....
}
4、編寫Mapper接口及xml文件
public interface BlogMapper {
}
<?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="com.kuang.mapper.BlogMapper">
</mapper>
5、mybatis核心配置文件,下划線駝峰自動轉換
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--注冊Mapper.xml-->
<mappers>
<mapper resource="mapper/BlogMapper.xml"/>
</mappers>
6、插入初始數據
編寫接口
//新增一個博客
int addBlog(Blog blog);
sql配置文件
<insert id="addBlog" parameterType="blog">
insert into blog (id, title, author, create_time, views)
values (#{id},#{title},#{author},#{createTime},#{views});
</insert>
初始化博客方法
@Test
public void addInitBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = new Blog();
blog.setId(IDUtil.genId());
blog.setTitle("Mybatis如此簡單");
blog.setAuthor("狂神說");
blog.setCreateTime(new Date());
blog.setViews(9999);
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Java如此簡單");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("Spring如此簡單");
mapper.addBlog(blog);
blog.setId(IDUtil.genId());
blog.setTitle("微服務如此簡單");
mapper.addBlog(blog);
session.close();
}
初始化數據完畢!
if 語句
需求:根據作者名字和博客名字來查詢博客!如果作者名字為空,那么只根據博客名字查詢,反之,則根據作者名來查詢
1、編寫接口類
//需求1
List<Blog> queryBlogIf(Map map);
2、編寫SQL語句
<!--需求1:
根據作者名字和博客名字來查詢博客!
如果作者名字為空,那么只根據博客名字查詢,反之,則根據作者名來查詢
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog where
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</select>
3、測試
@Test
public void testQueryBlogIf(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","Mybatis如此簡單");
map.put("author","狂神說");
List<Blog> blogs = mapper.queryBlogIf(map);
System.out.println(blogs);
session.close();
}
這樣寫我們可以看到,如果 author 等於 null,那么查詢語句為 select * from user where title=#{title},但是如果title為空呢?那么查詢語句為 select * from user where and author=#{author},這是錯誤的 SQL 語句,如何解決呢?請看下面的 where 語句!
Where
修改上面的SQL語句;
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</where>
</select>
這個“where”標簽會知道如果它包含的標簽中有返回值的話,它就插入一個‘where’。此外,如果標簽返回的內容是以AND 或OR 開頭的,則它會剔除掉。
Set
同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎么處理呢?
1、編寫接口方法
int updateBlog(Map map);
2、sql配置文件
<!--注意set是用的逗號隔開-->
<update id="updateBlog" parameterType="map">
update blog
<set>
<if test="title != null">
title = #{title},
</if>
<if test="author != null">
author = #{author}
</if>
</set>
where id = #{id};
</update>
3、測試
@Test
public void testUpdateBlog(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, String> map = new HashMap<String, String>();
map.put("title","動態SQL");
map.put("author","秦疆");
map.put("id","9d6a763f5e1347cebda43e2a32687a77");
mapper.updateBlog(map);
session.close();
}
choose語句
有時候,我們不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標簽可以解決此類問題,類似於 Java 的 switch 語句
1、編寫接口方法
List<Blog> queryBlogChoose(Map map);
2、sql配置文件
<select id="queryBlogChoose" parameterType="map" resultType="blog">
select * from blog
<where>
<choose>
<when test="title != null">
title = #{title}
</when>
<when test="author != null">
and author = #{author}
</when>
<otherwise>
and views = #{views}
</otherwise>
</choose>
</where>
</select>
3、測試類
@Test
public void testQueryBlogChoose(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("title","Java如此簡單");
map.put("author","狂神說");
map.put("views",9999);
List<Blog> blogs = mapper.queryBlogChoose(map);
System.out.println(blogs);
session.close();
}
SQL片段
有時候可能某個 sql 語句我們用的特別多,為了增加代碼的重用性,簡化代碼,我們需要將這些代碼抽取出來,然后使用時直接調用。
提取SQL片段:
<sql id="if-title-author">
<if test="title != null">
title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
</sql>
引用SQL片段:
<select id="queryBlogIf" parameterType="map" resultType="blog">
select * from blog
<where>
<!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
<include refid="if-title-author"></include>
<!-- 在這里還可以引用其他的 sql 片段 -->
</where>
</select>
注意:
①、最好基於 單表來定義 sql 片段,提高片段的可重用性
②、在 sql 片段中不要包括 where
Foreach
將數據庫中前三個數據的id修改為1,2,3;
需求:我們需要查詢 blog 表中 id 分別為1,2,3的博客信息
1、編寫接口
List<Blog> queryBlogForeach(Map map);
2、編寫SQL語句
<select id="queryBlogForeach" parameterType="map" resultType="blog">
select * from blog
<where>
<!--
collection:指定輸入對象中的集合屬性
item:每次遍歷生成的對象
open:開始遍歷時的拼接字符串
close:結束時拼接的字符串
separator:遍歷對象之間需要拼接的字符串
select * from blog where 1=1 and (id=1 or id=2 or id=3)
-->
<foreach collection="ids" item="id" open="and (" close=")" separator="or">
id=#{id}
</foreach>
</where>
</select>
3、測試
@Test
public void testQueryBlogForeach(){
SqlSession session = MybatisUtils.getSession();
BlogMapper mapper = session.getMapper(BlogMapper.class);
HashMap map = new HashMap();
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
map.put("ids",ids);
List<Blog> blogs = mapper.queryBlogForeach(map);
System.out.println(blogs);
session.close();
}
小結:其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接准確,我們最好首先要寫原生的 sql 語句出來,然后在通過 mybatis 動態sql 對照着改,防止出錯。多在實踐中使用才是熟練掌握它的技巧。
動態SQL在開發中大量的使用,一定要熟練掌握!
緩存
簡介
1、什么是緩存 [ Cache ]?
- 存在內存中的臨時數據。
- 將用戶經常查詢的數據放在緩存(內存)中,用戶去查詢數據就不用從磁盤上(關系型數據庫數據文件)查詢,從緩存中查詢,從而提高查詢效率,解決了高並發系統的性能問題。
2、為什么使用緩存?
- 減少和數據庫的交互次數,減少系統開銷,提高系統效率。
3、什么樣的數據能使用緩存?
- 經常查詢並且不經常改變的數據。
Mybatis緩存
-
MyBatis包含一個非常強大的查詢緩存特性,它可以非常方便地定制和配置緩存。緩存可以極大的提升查詢效率。
-
MyBatis系統中默認定義了兩級緩存:一級緩存和二級緩存
-
- 默認情況下,只有一級緩存開啟。(SqlSession級別的緩存,也稱為本地緩存)
- 二級緩存需要手動開啟和配置,他是基於namespace級別的緩存。
- 為了提高擴展性,MyBatis定義了緩存接口Cache。我們可以通過實現Cache接口來自定義二級緩存
一級緩存
一級緩存也叫本地緩存:
- 與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
- 以后如果需要獲取相同的數據,直接從緩存中拿,沒必須再去查詢數據庫;
測試
1、在mybatis中加入日志,方便測試結果
2、編寫接口方法
//根據id查詢用戶
User queryUserById(@Param("id") int id);
3、接口對應的Mapper文件
<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>
4、測試
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
5、結果分析
一級緩存失效的四種情況
一級緩存是SqlSession級別的緩存,是一直開啟的,我們關閉不了它;
一級緩存失效情況:沒有使用到當前的一級緩存,效果就是,還需要再向數據庫中發起一次查詢請求!
1、sqlSession不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
session2.close();
}
觀察結果:發現發送了兩條SQL語句!
結論:每個sqlSession中的緩存相互獨立
2、sqlSession相同,查詢條件不同
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
User user2 = mapper2.queryUserById(2);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
觀察結果:發現發送了兩條SQL語句!很正常的理解
結論:當前緩存中,不存在這個數據
3、sqlSession相同,兩次查詢之間執行了增刪改操作!
增加方法
//修改用戶
int updateUser(Map map);
編寫SQL
<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
</update>
測試
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
HashMap map = new HashMap();
map.put("name","kuangshen");
map.put("id",4);
mapper.updateUser(map);
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
觀察結果:查詢在中間執行了增刪改操作后,重新執行了
結論:因為增刪改操作可能會對當前數據產生影響
4、sqlSession相同,手動清除一級緩存
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.clearCache();//手動清除緩存
User user2 = mapper.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session.close();
}
一級緩存就是一個map
二級緩存
-
二級緩存也叫全局緩存,一級緩存作用域太低了,所以誕生了二級緩存
-
基於namespace級別的緩存,一個名稱空間,對應一個二級緩存;
-
工作機制
-
- 一個會話查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
- 如果當前會話關閉了,這個會話對應的一級緩存就沒了;但是我們想要的是,會話關閉了,一級緩存中的數據被保存到二級緩存中;
- 新的會話查詢信息,就可以從二級緩存中獲取內容;
- 不同的mapper查出的數據會放在自己對應的緩存(map)中;
使用步驟
1、開啟全局緩存 【mybatis-config.xml】
<setting name="cacheEnabled" value="true"/>
2、去每個mapper.xml中配置使用二級緩存,這個配置非常簡單;【xxxMapper.xml】
<cache/>
官方示例=====>查看官方文檔
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
這個更高級的配置創建了一個 FIFO 緩存,每隔 60 秒刷新,最多可以存儲結果對象或列表的 512 個引用,而且返回的對象被認為是只讀的,因此對它們進行修改可能會在不同線程中的調用者產生沖突。
3、代碼測試
- 所有的實體類先實現序列化接口
- 測試代碼
@Test
public void testQueryUserById(){
SqlSession session = MybatisUtils.getSession();
SqlSession session2 = MybatisUtils.getSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user = mapper.queryUserById(1);
System.out.println(user);
session.close();
User user2 = mapper2.queryUserById(1);
System.out.println(user2);
System.out.println(user==user2);
session2.close();
}
結論
- 只要開啟了二級緩存,我們在同一個Mapper中的查詢,可以在二級緩存中拿到數據
- 查出的數據都會被默認先放在一級緩存中
- 只有會話提交或者關閉以后,一級緩存中的數據才會轉到二級緩存中
緩存原理圖
EhCache
第三方緩存實現--EhCache: 查看百度百科
Ehcache是一種廣泛使用的java分布式緩存,用於通用緩存;
要在應用程序中使用Ehcache,需要引入依賴的jar包
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.1.0</version>
</dependency>
在mapper.xml中使用對應的緩存即可
<mapper namespace = “org.acme.FooMapper” >
<cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
</mapper>
編寫ehcache.xml文件,如果在加載時未找到/ehcache.xml資源或出現問題,則將使用默認配置。
<?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:為緩存路徑,ehcache分為內存和磁盤兩級,此屬性定義磁盤的緩存位置。參數解釋如下:
user.home – 用戶主目錄
user.dir – 用戶當前工作目錄
java.io.tmpdir – 默認臨時文件路徑
-->
<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"/>
<!--
defaultCache:默認緩存策略,當ehcache找不到定義的緩存時,則使用這個緩存策略。只能定義一個。
-->
<!--
name:緩存名稱。
maxElementsInMemory:緩存最大數目
maxElementsOnDisk:硬盤最大緩存個數。
eternal:對象是否永久有效,一但設置了,timeout將不起作用。
overflowToDisk:是否保存到磁盤,當系統當機時
timeToIdleSeconds:設置對象在失效前的允許閑置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閑置時間無窮大。
timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。
diskPersistent:是否緩存虛擬機重啟期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩沖區。
diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。
memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置為FIFO(先進先出)或是LFU(較少使用)。
clearOnFlush:內存數量最大時是否清除。
memoryStoreEvictionPolicy:可選策略有:LRU(最近最少使用,默認策略)、FIFO(先進先出)、LFU(最少訪問次數)。
FIFO,first in first out,這個是大家最熟的,先進先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。
LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那么現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。
-->
</ehcache>
合理的使用緩存,可以讓我們程序的性能大大提升!