寫在前面: Mybatis是一個優秀的框架,今天我給大家介紹一下mybatis的用法,本文特別長,可以和官方文檔一起食用。並且我這裏整理了一份PDF格式的筆記,需要可以找我,全文8W字符,寫得很累,希望能給個贊。
公衆號:小白編碼。
本文目錄
- Mybatis介紹:
- 第一章:什麼是框架
- 第二章:JDBC回顧
- 第三章:Mybatis使用
- 第四章:★Mybatis全局配置文件
- 4.1.1 properties標籤
- 4.1.2 ★settings標籤
- 4.1.3 ★駝峯命名開啓
- 4.1.4 typeAliases別名處理器
- 4.1.5 ★environments標籤
- 4.1.6 databaseIdProvide標籤
- 4.1.7 ★mappers標籤映射註冊
- 4.1.8同一個包的概念:
- 第五章:★Mybatis關係映射文件
- 5.1.1 ★sql關係映射文件的CRUD
- 5.1.2 MySQL獲取主鍵:
- 5.2.1 Mybatis參數處理★
- 5.3.1 ★#{}與${}的區別:
- 5.4.1 ★select標籤
- 5.5.1 多表查詢
- 5.6.1 鑑別器
- 第六章:★動態SQL
- 6.1.1 if判斷標籤與where標籤
- 6.1.2 trim標籤
- 6.1.3 choose標籤
- 6.1.4 set標籤
- 6.1.5 foreach標籤與批量插入
- 6.1.6 抽取可重複sql的值
- 6.1.7 內置參數:
- 6.1.8 bink標籤
- 第七章:★Mybatis緩存
- 第八章:★Mybatis插件開發
- 第九章: 拓展功能
- 寫在後邊:
Mybatis介紹:
mybatis是一個優秀的基於java的持久層框架,它內部封裝了jdbc,使開發者只需要關注sql語句本身,而不需要花費精力去處理加載驅動、創建連接、創建statement等繁雜的過程。 mybatis通過xml或註解的方式將要執行的各種statement配置起來,並通過java對象和statement中sql的動態參數進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射爲java對象並返回。 採用ORM思想解決了實體和數據庫映射的問題,對jdbc進行了封裝,屏蔽了jdbc api底層訪問細節,使我們不用與jdbc api打交道,就可以完成對數據庫的持久化操作。 爲了我們能夠更好掌握框架運行的內部過程,並且有更好的體驗,下面我們將從自定義Mybatis框架開始來學習框架。此時我們將會體驗框架從無到有的過程體驗,也能夠很好的綜合前面階段所學的基礎。
而且:Mybatis中sql和java編碼分開,功能邊界清晰,一個專注業務、一個專注數據。
第一章:什麼是框架
1.1.1框架概述
框架(Framework)是整個或部分系統的可重用設計,表現爲一組抽象構件及構件實例間交互的方法;另一種定義認爲,框架是可被應用開發者定製的應用骨架。前者是從應用方面而後者是從目的方面給出的定義。 簡而言之,框架其實就是某種應用的半成品,就是一組組件,供你選用完成你自己的系統。簡單說就是使用別人搭好的舞臺,你來做表演。而且,框架一般是成熟的,不斷升級的軟件。
1.1.2框架要解決的問題
框架要解決的最重要的一個問題是技術整合的問題,在J2EE的 框架中,有着各種各樣的技術,不同的軟件企業需要從J2EE中選擇不同的技術,這就使得軟件企業最終的應用依賴於這些技術,技術自身的複雜性和技術的風險性將會直接對應用造成衝擊。而應用是軟件企業的核心,是競爭力的關鍵所在,因此應該將應用自身的設計和具體的實現技術解耦。這樣,軟件企業的研發將集中在應用的設計上,而不是具體的技術實現,技術實現是應用的底層支撐,它不應該直接對應用產生影響。 框架一般處在低層應用平臺(如J2EE)和高層業務邏輯之間的中間層。
第二章:JDBC回顧
2.1.1JDBC編程的分析
2.1.2JDBC程序編寫步驟
補充:ODBC(Open Database Connectivity,開放式數據庫連接),是微軟在Windows平臺下推出的。使用者在程序中只需要調用ODBC API,由 ODBC 驅動程序將調用轉換成爲對特定的數據庫的調用請求。
2.1.3獲取數據庫連接
首先導入Maven數據庫驅動:
<dependency>
<!--MySQL驅動包-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
public static void main(String[] args) {
Connection conn = null;
try {
//加載配置文件
InputStream is = JDBCTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
//2.讀取配置信息
String username = properties.getProperty("username");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//3.加載驅動
Class.forName(driverClass);
//獲取連接
conn = DriverManager.getConnection(url, username, password);
System.out.println(conn);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
assert conn != null;
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
其中,配置文件聲明在工程的src目錄下:【jdbc.properties】
user=root
password=abc
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver
2.1.4使用PreparedStatement實現CRUD操作
使用PreparedStatement
實現增、刪、改操作
//通用的增、刪、改操作(體現一:增、刪、改 ; 體現二:針對於不同的表)
public void update(String sql,Object ... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//1.獲取數據庫的連接
conn = JDBCUtils.getConnection();
//2.獲取PreparedStatement的實例 (或:預編譯sql語句)
ps = conn.prepareStatement(sql);
//3.填充佔位符
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
//4.執行sql語句
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}finally{
//5.關閉資源
JDBCUtils.closeResource(conn, ps);
}
}
使用PreparedStatement
實現查詢操作
public <T> T getInstance(Class<T> clazz, String sql, Object... args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 1.獲取數據庫連接
conn = JDBCUtils.getConnection();
// 2.預編譯sql語句,得到PreparedStatement對象
ps = conn.prepareStatement(sql);
// 3.填充佔位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 4.執行executeQuery(),得到結果集:ResultSet
rs = ps.executeQuery();
// 5.得到結果集的元數據:ResultSetMetaData
ResultSetMetaData rsmd = rs.getMetaData();
// 6.1通過ResultSetMetaData得到columnCount,columnLabel;通過ResultSet得到列值
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {// 遍歷每一個列
// 獲取列值
Object columnVal = rs.getObject(i + 1);
// 獲取列的別名:列的別名,使用類的屬性名充當
String columnLabel = rsmd.getColumnLabel(i + 1);
// 6.2使用反射,給對象的相應屬性賦值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 7.關閉資源
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
2.1.5 JDBC總結
總結
@Test
public void testUpdateWithTx() {
Connection conn = null;
try {
//1.獲取連接的操作(
//① 手寫的連接:JDBCUtils.getConnection();
//② 使用數據庫連接池:C3P0;DBCP;Druid
//2.對數據表進行一系列CRUD操作
//① 使用PreparedStatement實現通用的增刪改、查詢操作(version 1.0 \ version 2.0)
//version2.0的增刪改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查詢 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
//② 使用dbutils提供的jar包中提供的QueryRunner類
//提交數據
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//回滾數據
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
}finally{
//3.關閉連接等操作
//① JDBCUtils.closeResource();
//② 使用dbutils提供的jar包中提供的DbUtils類提供了關閉的相關操作
}
}
JDBC缺點:
SQL夾在Java代碼塊裏,耦合度高導致硬編碼內傷
維護不易且實際開發需求中sql是有變化,頻繁修改的情況多見
第三章:Mybatis使用
3.1.1環境搭建與測試
sql測試表準備:(自行插入數據)
CREATE TABLE `tbl_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` varchar(255) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`d_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_emp_dept` (`d_id`),
CONSTRAINT `fk_emp_dept` FOREIGN KEY (`d_id`) REFERENCES `tbl_dept` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
第一步pom.xml創建座標導入依賴:
<dependency>
<!--MyBatis框架Jar包-->
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<!--MySQL驅動包-->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<!--log4j日誌包-->
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<!--junit測試包-->
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
放入log4j.properties:
# Set root category priority to INFO and its only appender to CONSOLE.
#log4j.rootCategory=INFO, CONSOLE debug info warn error fatal
log4j.rootCategory=debug, CONSOLE, LOGFILE
# Set the enterprise logger category to FATAL and its only appender to CONSOLE.
log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
# LOGFILE is set to be a File appender using a PatternLayout.
log4j.appender.LOGFILE=org.apache.log4j.FileAppender
log4j.appender.LOGFILE.File=d:axis.log
log4j.appender.LOGFILE.Append=true
log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
第二步:創建實體類:
@Data
public class Employee{
private Integer id;
private String lastName;
private String email;
private Integer gender;
}
dao的接口:
public interface EmployeeMapper {
/**
* 查詢所有操作
*/
List<Employee> getAllEmp();
}
第三步: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">
<!--MyBatis主配置文件-->
<configuration>
<!--配置環境-->
<environments default="dev_mysql">
<!--配置mysql的環境,可以配置多個數據庫-->
<environment id="dev_mysql">
<!--配置事務的類型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置連接數據庫的4個基本信息-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123"/>
</dataSource>
</environment>
</environments>
<!-- 告知mybatis映射配置的位置 -->
<mappers>
<mapper resource="cn/codewhite/dao/EmployeeMapper.xml"/>
</mappers>
</configuration>
第四步:創建映射配置文件EmployeeMapper.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">
<!--配置查詢所有(是Dao裏面的方法)-->
<mapper namespace="cn.codewhite.dao.EmployeeMapper">
<!-- List<Employee> getAllEmp();-->
<select id="getAllEmp" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee
</select>
</mapper>
案例測試:
@Test
public void test01() {
InputStream is = null;
SqlSession sqlSession = null;
try {
// 第一步:讀取配置文件(獲取輸入流)
is = Resources.getResourceAsStream("mybatis-config.xml");
// 第二步:創建SqlSessionFactory工廠
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(is);
// 第三步:創建SqlSession.openSession();
sqlSession = sqlSessionFactory.openSession();
// 第四步:創建Dao接口的代理對象
EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
// 第五步:執行dao中的方法
List<Employee> allEmp = empMapper.getAllEmp();
//打印:
allEmp.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 第六步:釋放資源
sqlSession.close();
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
如果遇到bug:
1.在mybatis-config.xml沒有告知mybatis映射配置的位置
Exception in thread "main" org.apache.ibatis.binding.BindingException: Type interface cn.codewhite.dao.EmployeeMapper is not known to the MapperRegistry.
答案:在SqlMapConifg.xml加入以下內容
<!-- 告知mybatis關係映射配置的位置 -->
<mappers>
<mapper resource="cn/codewhite/dao/EmployeeMapper.xml"/>
</mappers>
★ 總結:
第一步:創建maven工程並導入座標依賴
第二步:創建實體類和dao的接口
第三步:創建Mybatis的主配置文件
mybatis-config.xml
第四步:創建映射配置文件
EmployeeMapper.xml
環境搭建的注意事項:
第一個:創建EmployeeMapper.xml和 EmployeeMapper.java時名稱是爲了和我們之前的知識保持一致。
在Mybatis中它把持久層的操作接口名稱和映射文件也叫做:Mapper
所以:EmployeeDao 和 EmployeeMapper是一樣的
第二個:在idea中創建目錄的時候,它和包是不一樣的
包在創建時:com.itheima.dao它是三級結構
目錄在創建時:com.itheima.dao是一級目錄
第三個:mybatis的映射配置文件位置必須和dao接口的包結構相同
第四個:映射配置文件的mapper標籤namespace屬性的取值必須是dao接口的全限定類名
第五個:映射配置文件的操作配置(select),id屬性的取值必須是dao接口的方法名
當我們遵從了第三,四,五點之後,我們在開發中就無須再寫dao的實現類。
mybatis的入門案例
第一步:讀取配置文件
第二步:創建SqlSessionFactory工廠
第三步:創建SqlSession
第四步:創建Dao接口的代理對象
第五步:執行dao中的方法
第六步:釋放資源
3.1.2★小結問題:
1、接口式編程
原生: Dao ====> DaoImpl
mybatis: Mapper ====> xxMapper.xml
2、SqlSession代表和數據庫的一次會話;用完必須關閉;close
3、SqlSession和connection一樣它都是非線程安全。每次使用都應該去獲取新的對象。
4、mapper接口沒有實現類,但是mybatis會爲這個接口生成一個代理對象。
(將接口和xml進行綁定)
EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);
5、兩個重要的配置文件:
mybatis的全局配置文件:包含數據庫連接池信息,事務管理器信息等…系統運行環境信息
sql映射文件:保存了每一個sql語句的映射信息:
將sql抽取出來。
- 1、根據xml配置文件(全局配置文件)創建一個SqlSessionFactory對象 有數據源一些運行環境信息
- 2、sql映射文件;配置了每一個sql,以及sql的封裝規則等。
- 3、將sql映射文件註冊在全局配置文件中
- 4、寫代碼:
- 1)、根據全局配置文件得到SqlSessionFactory;
- 2)、使用sqlSession工廠,獲取到sqlSession對象使用他來執行增刪改查
- 一個sqlSession就是代表和數據庫的一次會話,用完關閉
- 3)、使用sql的唯一標誌來告訴MyBatis執行哪個sql。sql都是保存在sql映射文件中的。
sql關係映射文件:存在dao包下的:EmployeeMapper.xml
全局配置文件:mybatis-config.xml
,並且在全局配置文件中不要忘了寫映射文件的地址
第四章:★Mybatis全局配置文件
配置文件配置項順序:
properties→settings→typeAliases→typeHandlers→objectFactory→objectWrapperFactory→reflectorFactory→plugins→environments→databaseIdProvider→mappers。
4.1.1 properties標籤
<properties></properties>
標籤:
在配置文件內配置數據庫環境,可以通過外部的properties配置文件引入信息。
標籤屬性:
resource:引入類路徑下的資源:如果在某包下:cn/codewhite/xxxxx
url:引入網絡路徑或者磁盤路徑下的資源
<!--properties標籤-->
<properties resource="jdbconfig.properties"></properties>
<!--配置環境-->
<environments default="dev_mysql">
<!--配置mysql的環境,可以配置多個數據庫-->
<environment id="dev_mysql">
<!--配置事務的類型-->
<transactionManager type="JDBC"></transactionManager>
<!--配置連接數據庫的4個基本信息-->
<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>
</environments>
properties:
jdbc.username=root
jdbc.password=123
jdbc.url=jdbc:mysql://localhost:3306/eesy_mybatis
jdbc.driver=com.mysql.jdbc.Driver
4.1.2 ★settings標籤
<settings></settings>
標籤:
settings包含很多重要的設置項
setting:用來設置每一個設置項
name:設置項名
value:設置項取值
4.1.3 ★駝峯命名開啓
駝峯命名對應開啓:mapUnderscoreToCamelCase
在我的數據庫表中姓名命名爲:last_name,而在JavaBean中:lastName
在不使用別名的情況下,沒有開啓駝峯命名它就無法封裝信息:
如何開啓:全局配置文件中加入settings
配置:mapUnderscoreToCamelCase
爲true
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
4.1.4 typeAliases別名處理器
typeAliases:別名處理器:可以爲我們的java類型起別名,別名不區分大小寫
全局配置文件:
1、typeAlias:爲某個java類型起別名
type:指定要起別名的類型全類名;默認別名就是類名小寫;employee
alias:指定新的別名
2、package:爲某個包下的所有類批量起別名
name:指定包名(爲當前包以及下面所有的後代包的每一個類都起一個默認別名(類名小寫))
3、批量起別名的情況下,使用@Alias註解爲某個類型指定新的別名
<typeAliases>
<!--type給指定的類起別名,如果沒有寫alias指定,
那麼默認別名爲該類名的全小寫employee
alias:指定新的別名
-->
<!--<typeAlias type="cn.codewhite.pojo.Employee" alias="emp"></typeAlias>-->
<!-- package:爲某個包下的所有類批量起別名
name:指定包名(爲當前包以及下面所有的後代包的每一個類都起一個默認別名(類名小寫),)
-->
<package name="cn.codewhite.pojo"/>
</typeAliases>
批量起別名,使用@Alias註解爲某個類型指定新的別名
@Alias("emp)
public class Employee implements Serializable {
別名使用:在sql關係映射文件中使用別名
<!-- List<Employee> getAllEmp();-->
<select id="getAllEmp" resultType="emp">
select * from tbl_employee
</select>
注意事項: 在使用註解別名的時候,必須要開啓批量別名模式,否則報錯。
4.1.5 ★environments標籤
environments
標籤:環境,mybatis可以配置多種環境 ,default指定使用某種環境。可以達到快速切換環境。
transactionManager
事務管理器
type事務管理器的類型
1.JDBC
(JdbcTransactionFactory):使用JDBC的方式進行事務的回滾以及提交等
2.MANAGED
(ManagedTransactionFactory):使用JEE服務器容器的方式進行事務控制,
其實是兩個別名,這裏一般用Spring來配置
自定義事務管理器:實現TransactionFactory接口.type指定爲全類名。
dataSource
數據源:
type
:數據源類型
1.UNPOOLED
(UnpooledDataSourceFactory):不使用連接池技術,從數據庫拿連接
2.POOLED
(PooledDataSourceFactory):使用數據庫連接池技術
3.JNDI
(JndiDataSourceFactory)使用JNDI連接技術
可以自定義德魯伊,C3P0數據庫連接池等
自定義數據源:實現DataSourceFactory接口,type是全類名
其實在Configuration中定義的都是別名:
<!--配置環境,default使用mysql環境-->
<environments default="dev_mysql">
<!--配置mysql的環境,可以配置多個數據庫-->
<environment id="dev_mysql">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<!--配置連接數據庫的4個基本信息-->
<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="dev_oracle">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.username}" />
<property name="password" value="${orcl.password}" />
</dataSource>
</environment>
</environments>
4.1.6 databaseIdProvide標籤
databaseIdProvide
支持多數據庫廠商的,作用就是得到數據庫廠商的標識
type="DB_VENDOR"
:VendorDatabaseIdProvider (驅動getDatabaseProductName()),
mybatis就能根據數據庫廠商標識來執行不同的sql;
一般是:
MySQL,Oracle,SQL Server
全局配置文件設置:
<databaseIdProvider type="DB_VENDOR">
<!-- 爲不同的數據庫廠商起別名 -->
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
在sql映射文件中,輸入databaseId="別名"指定使用的數據庫:
<!-- List<Employee> getAllEmp();-->
<select id="getAllEmp" resultType="emp" databaseId="mysql">
select * from tbl_employee
</select>
默認加載所有不帶規則的SQL語句,如果當前是mysql,一般加載更精確的sql語句,(指的是標識)
4.1.7 ★mappers標籤映射註冊
<mappers></mappers>
標籤映射註冊:在全局配置文件中註冊。
mapper
標籤:註冊一個sql映射
註冊配置文件方式:
resource
:引用類路徑下的sql映射文件,如:mybatis/mapper/EmployeeMapper.xml
url
:引用網路路徑或者磁盤路徑下的sql映射文件如:file:///var/mappers/AuthorMapper.xml
註冊接口方式:
class
:引用(註冊)接口,
1、有sql映射文件,映射文件名必須和接口同名,並且放在與接口同一目錄下;
2、沒有sql映射文件,所有的sql都是利用註解寫在接口上;
推薦:
比較重要的,複雜的Dao接口我們來寫sql映射文件
不重要,簡單的Dao接口爲了開發快速可以使用註解.
將我們寫好的sql映射文件(EmployeeMapper.xml)一定要註冊到全局配置文件(mybatis-config.xml)中
全局配置文件:(註冊方式選一種就好)
<!-- 告知mybatis映射配置的位置 -->
<mappers>
<!-- 註冊單個映射配置文件-->
<!-- <mapper resource="cn/codewhite/dao/EmployeeMapper.xml"/>-->
<!-- 註冊接口模式,有sql映射文件的情況-->
<!-- <mapper class="cn.codewhite.dao.EmployeeMapper"/>-->
<!-- 註冊接口,沒有關係映射文件,但是有註解的方式-->
<!-- <mapper class="cn.codewhite.dao.EmployeeMapperAnnotation"/>-->
<!-- 批量註冊:註冊一個包下的所有映射 -->
<!-- 接口也會註冊!注意,前提是接口和關係映射文件需要在同一個目錄或包下-->
<package name="cn.codewhite.dao"/>
</mappers>
這裏需要講的是:
註冊接口註解開發模式:
java代碼: 寫上註解
public interface EmployeeMapperAnnotation {
@Select("select * from tbl_employee where id = #{id}")
Employee getEmpById(Integer id);
}
註冊接口模式:(有sql映射文件,映射文件名必須和接口同名,並且放在與接口同一目錄下;)
4.1.8同一個包的概念:
下圖這樣屬於同一個包。
在Maven工程中:其實這兩個也是算同一個包內:
mybatis的映射配置文件位置必須和dao接口的包結構相同。
因爲所有的文件都放在Bin目錄下,只要我設置了3層目錄,所以也等同於同一個目錄
在idea中創建目錄的時候,它和包是不一樣的
包在創建時:com.itheima.dao它是三級結構
目錄在創建時:com.itheima.dao是一級目錄
第五章:★Mybatis關係映射文件
mapper
常用屬性:
-
namespace:名稱空間;指定爲接口的全類名
-
id:唯一標識
-
resultType:返回值類型
-
#{id}:從傳遞過來的參數中取出id值
-
parameterType參數類型
5.1.1 ★sql關係映射文件的CRUD
mapper接口:
public interface EmployeeMapper {
List<Employee> getAllEmp();
void addEmp(Employee emp);
int updateEmp(Employee emp);
boolean deleteEmpById(Integer id);
}
sql關係映射文件:
<?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">
<!--配置查詢所有(是Dao裏面的方法)
namespace:名稱空間;指定爲接口的全類名
-->
<mapper namespace="cn.codewhite.dao.EmployeeMapper">
<!-- List<Employee> getAllEmp();
id:唯一標識
resultType:返回值類型
-->
<select id="getAllEmp" resultType="emp" databaseId="mysql">
select * from tbl_employee
</select>
<!-- void addEmp(Employee emp);
parameterType參數類型
#{id}:從傳遞過來的參數中取出id值
-->
<insert id="addEmp" parameterType="cn.codewhite.pojo.Employee">
insert into tbl_employee(last_name,email,gender)
values (#{lastName},#{email},#{gender})
</insert>
<!-- int updateEmp(Employee emp);-->
<update id="updateEmp" parameterType="cn.codewhite.pojo.Employee">
update tbl_employee
set last_name = #{lastName},email = #{email},gender = #{gender}
where id = #{id}
</update>
<!-- boolean deleteEmpById(Integer id);-->
<delete id="deleteEmpById" parameterType="cn.codewhite.pojo.Employee">
delete from tbl_employee where id = #{id}
</delete>
CRUD前:
CRUD後:
CRUD測試代碼:
@Test
public void testCRUD() {
SqlSession sqlSession = null;
try {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//1、獲取到的SqlSession不會自動提交數據
sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//查詢
List<Employee> allEmp = mapper.getAllEmp();
allEmp.forEach(System.out::println);
//插入1個員工
mapper.addEmp(new Employee(null, "新插入員工", "[email protected]", 1, null));
//修改4號員工
System.out.println(mapper.updateEmp(new Employee(4, "4號員工", "[email protected]", 1, null)));
//刪除1號員工
System.out.println(mapper.deleteEmpById(1));
//2、提交數據
sqlSession.commit();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
注意問題: 增刪改需要提交事務!!!否則無法生效
5.1.2 MySQL獲取主鍵:
mysql支持自增主鍵,自增主鍵值的獲取,mybatis也是利用statement.getGenreatedKeys();
useGeneratedKeys="true"
;使用自增主鍵獲取主鍵值策略
keyProperty
;指定對應的主鍵屬性,也就是mybatis獲取到主鍵值以後,將這個值封裝給javaBean的哪個屬性
<insert id="addEmp" parameterType="cn.codewhite.pojo.Employee"
useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(last_name,email,gender)
values (#{lastName},#{email},#{gender})
</insert>
測試代碼:此時就能夠獲取剛剛add的員工的id了
//插入1個員工
Employee employee = new Employee(null, "新插入員工3", "[email protected]", 0, null);
mapper.addEmp(employee);
System.out.println(employee.getId());
打印結果:14
5.2.1 Mybatis參數處理★
何爲參數: 參數就是接口方法裏定義的形參,然後需要在xml關係映射文件中使用#{參數名}獲取參數。
如:Mapper接口方法:
Employee getEmpById(Integer id)
映射文件: 其中#{id}就是獲取參數
<select id="getEmpById" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{id}
</select>
5.2.2 單個參數:
mybatis不會做特殊處理,
因爲只有一個參數
#{參數名/任意名}:取出參數值。
Mapper映射文件:#{id}可以寫成#{任意名}
原因:在Mapper接口只定義了一個參數Employee getEmpById(Integer id);
關係映射文件: 此時的#{id}可以寫成任何名,如:#{idxxxx}都能夠識別參數id
<select id="getEmpById" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{id}
</select>
測試:
getEmpById(1)結果:
Employee [id=1, lastName=Tom, email=jerry@atguigu.com, gender=0]
5.2.3 多個參數:
mybatis會做特殊處理。
多個參數會被封裝成 一個map
key
:param1…paramN,或者參數的索引也可以
value
:傳入的參數值
#{}
就是從map中獲取指定的key
的值;
接口方法:Employee getEmpByIdAndLastName(Integer id,String lastName);
若直接使用#{id},#{lastName}取值,那麼會直接報異常:
org.apache.ibatis.binding.BindingException:
Parameter 'id' not found.
Available parameters are [1, 0, param1, param2]
解決方法:使用命名參數,使用多個參數處理。
如何使用多個參數處理:
sql關係映射文件配置:
<select id="getEmpByIdAndLastName" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{param1} and last_name = #{param2}
</select>
或者:
<select id="getEmpByIdAndLastName" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{arg0} and last_name = #{arg1}
</select>
5.2.4 使用命名參數:
明確指定封裝參數時map的key;使用註解:@Param("id")
多個參數會被封裝成 一個map,
key
:使用@Param註解指定的值
value
:參數值
#{指定的key}
取出對應的參數值
如何使用: 在方法裏添加註解@Param("xxx")
註解方式:
EmployeeMapper接口方法:
Employee getEmpByIdAndLastName(@Param("id") Integer id, @Param("lastName") String lastName);
sql關係映射文件:此時就可以直接使用#{註解中定義的參數名}
<select id="getEmpByIdAndLastName" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
Map方式解決:
EmployeeMapper接口方法:
Employee getEmpByMap(Map<String, Object> map);
sql關係映射文件配置:
<select id="getEmpByMap" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
測試使用:通過map自定義key-value值來配置參數。
@Test
public void testMap(){
SqlSession sqlSession = null;
try {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//重點:使用map定製,向map中放入指定的屬性與屬性值
Map<String, Object> map = new HashMap<>();
map.put("id",5);
map.put("lastName","xiaobai");
System.out.println(mapper.getEmpByMap(map));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
測試結果:Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=null)
5.2.5 參數處理推薦使用方法:
推薦的用法:POJO,MAP,TO。
POJO使用如下:
EmployeeMapper接口方法定義:
Employee getEmpByIdAndLastName(Employee employee);
XML:
<select id="getEmpByIdAndLastName" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where id = #{id} and last_name = #{lastName}
</select>
測試方法:通過javabean的屬性來查詢
@Test
public void getEmpByIdAndLastName() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee tom = mapper.getEmpByIdAndLastName(new Employee(1, "Tom", null, null));
System.out.println(tom);
}
MAP使用: 就是5.2.4裏的map方法。
TO使用:
如果多個參數不是業務模型中的數據,但是經常要使用,推薦來編寫一個TO(Transfer Object)數據傳輸對象
Page{
int index;
int size;
}
5.2.6 ★參數處理總結:
public Employee getEmp(@Param("id")Integer id,String lastName);
那麼xml中取值:id==>#{id/param1} lastName==>#{param2}
public Employee getEmp(Integer id,@Param("e")Employee emp);
那麼xml中取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}
注意: 如果是Collection(List、Set)類型或者是數組,
也會特殊處理。也是把傳入的list或者數組封裝在map中。
key:Collection(collection),如果是List還可以使用這個key(list)
數組(array)
public Employee getEmpById(List<Integer> ids);
5.3.1 ★#{}與${}的區別:
#{}
:可以獲取map中的值或者pojo對象屬性的值;
${}
:可以獲取map中的值或者pojo對象屬性的值;
xml映射文件:
select * from tbl_employee where id=${id} and last_name=#{lastName}
執行結果:Preparing: select * from tbl_employee where id=2 and last_name=?
以上測試代碼發現:
#{}
:是以預編譯的形式,將參數設置到sql語句中;PreparedStatement;防止sql注入
${}
:取出的值直接拼裝在sql語句中;會有安全問題;
使用:原生jdbc不支持佔位符的地方我們就可以使用${}進行取值
比如分表、排序…;按照年份分表拆分
select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}
#{}:更豐富的用法:
規定參數的一些規則:
javaType
、 jdbcType
、 mode(存儲過程)
、 numericScale、
resultMap
、typeHandler
、jdbcTypeName
、 expression
(未來準備支持的功能);
jdbcType通常需要在某種特定的條件下被設置:
在我們數據爲null的時候,有些數據庫可能不能識別mybatis對null的默認處理。比如Oracle(報錯);
JdbcType OTHER:無效的類型;因爲mybatis對所有的null都映射的是原生Jdbc的OTHER類型,oracle不能正確處理;
由於全局配置中:jdbcTypeForNull=OTHER
;oracle不支持;兩種辦法
1、#{email,jdbcType=OTHER};
2、jdbcTypeForNull=NULL
<setting name="jdbcTypeForNull" value="NULL"/>
5.4.1 ★select標籤
- Id:唯一標識符。
–用來引用這條語句,需要和接口的方法名一致 - parameterType:參數類型。
–可以不傳,MyBatis會根據TypeHandler自動推斷 - resultType:返回值類型。
–別名或者全類名,如果返回的是集合,定義集合中元素的類型。不能和resultMap同時使用
5.4.2 返回值類型爲List集合
EmployeeMapper.xml:
<!--resultType:如果返回的是一個集合,要寫集合中元素的類型,這裏返回Employee類型 -->
<select id="getEmpsByLastNameLike" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where last_name like #{lastName}
</select>
EmployeeMapper接口:
List<Employee> getEmpsByLastNameLike(String lastName);
測試方法:
@Test
public void test05() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
//會爲接口自動的創建一個代理對象,代理對象去執行增刪改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
List<Employee> empsByLastNameLike = mapper.getEmpsByLastNameLike("%t%");
System.out.println(empsByLastNameLike);
} finally {
openSession.close();
}
}
測試結果:
[Employee [id=1, lastName=Tom, email=jerry@atguigu.com, gender=0]]
5.4.3 返回值類型爲Map集合
EmployeeMapper.xml:
<!--public Map<String, Object> getEmpByIdReturnMap(Integer id); -->
<select id="getEmpByIdReturnMap" resultType="map">
select * from tbl_employee where id = #{id}
</select>
EmployeeMapper接口:
//返回一條記錄的map;key就是列名,值就是對應的值
Map<String, Object> getEmpByIdReturnMap(Integer id);
測試方法:
@Test
public void test06() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
//會爲接口自動的創建一個代理對象,代理對象去執行增刪改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Map<String, Object> empByIdReturnMap = mapper.getEmpByIdReturnMap(1);
System.out.println(empByIdReturnMap);
} finally {
openSession.close();
}
}
結果:
//key=value&key=vlaue,其中key就是列名
{gender=1, d_id=1, last_name=xiaobai, id=5, email=xiaobai@qq.com}
5.4.4 自定義映射規則:
select
標籤:使用自定義某個javaBean的封裝規則
type
:自定義規則的Java類型
id
:唯一id方便引用
resultMap
自定義封裝規則
指定主鍵列的封裝規則
id定義主鍵會底層有優化;
column:指定哪一列
property:指定對應的javaBean屬性
映射文件:
<resultMap id="formatMap" type="cn.codewhite.pojo.Employee">
<!--指定主鍵列的封裝規則
id定義主鍵會底層有優化;
column:指定哪一列
property:指定對應的javaBean屬性
-->
<id column="id" property="id"/>
<!-- 定義普通列封裝規則 -->
<result column="last_name" property="lastName"/>
<!-- 其他不指定的列也會自動封裝,但是最好寫上 -->
<result column="email" property="email"/>
<!-- <result column="gender" property="gender"/>-->
</resultMap>
<!-- resultMap:自定義結果集映射規則; -->
<select id="getEmpById" resultMap="formatMap">
select * from tbl_employee where id=#{id}
</select>
EmployeeMapper接口:
Employee getEmpById(Integer id);
測試結果:
@Test
public void testFormatMap() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee empById = mapper.getEmpById(5);
System.out.println(empById);
} finally {
openSession.close();
}
}
測試結果:Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=null)
5.5.1 多表查詢
5.5.2:mysql複習:
聯合查詢:
union 聯合 合併:將多條查詢語句的結果合併成一個結果
語法:
查詢語句1
union
查詢語句2
union
應用場景:
要查詢的結果來自於多個表,且多個表沒有直接的連接關係,但查詢的信息一致時
特點:★
1、要求多條查詢語句的查詢列數是一致的!
2、要求多條查詢語句的查詢的每一列的類型和順序最好一致
3、union關鍵字默認去重,如果使用union all 可以包含重複項
連接查詢:
語法:
select 查詢列表
from 表1 別名 【連接類型】
join 表2 別名
on 連接條件
【where 篩選條件】
【group by 分組】
【having 篩選條件】
【order by 排序列表】
分類:
內連接(★):inner
外連接
左外(★):left 【outer】
右外(★):right 【outer】
全外:full【outer】
交叉連接:cross
表的修改:
語法
alter table 表名 add|drop|modify|change column 列名 【列類型 約束】;
alter table 表明 add|drop|change|modify
#①修改列名
ALTER TABLE book CHANGE COLUMN publishdate pubDate DATETIME;
#②修改列的類型或約束
ALTER TABLE book MODIFY COLUMN pubdate TIMESTAMP;
#③添加新列
ALTER TABLE author ADD COLUMN annual DOUBLE;
#④刪除列
ALTER TABLE book_author DROP COLUMN annual;
#⑤修改表名
ALTER TABLE author RENAME TO book_author;
DESC book;
多表環境搭建:Department:JavaBean
@Data
public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
}
Employee下添加
private Department dept;
SQL創建:
CREATE TABLE `tbl_dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dept_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
插入數據:
INSERT INTO `eesy_mybatis`.`tbl_dept`(`dept_name`) VALUES ('測試部')
INSERT INTO `eesy_mybatis`.`tbl_dept`(`dept_name`) VALUES ('開發部')
添加外鍵:
ALTER TABLE tbl_employee ADD CONSTRAINT
fk_emp_dept FOREIGN KEY(d_id) REFERENCES
tbl_dept(id);
添加:約束
UPDATE `eesy_mybatis`.`tbl_employee` SET `d_id` = 2 WHERE `id` = 3
表結構:
tbl_employee:
tbl_dept:
5.5.3:關聯查詢:
需求:查詢指定的Employee的同時查詢員工對應的部門
Employee===Department
一個員工有與之對應的部門信息;
id last_name gender d_id did dept_name (private Department dept;)
EmployeeMapper接口:
Employee getEmpAndDept(Integer id);
EmployeeMapper.xml:
第一種封裝: 聯合查詢:級聯屬性封裝結果集
<resultMap type="cn.codewhite.pojo.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="did" property="dept.id"/>
<!--可以指定是dept下的departmentName屬性-->
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.email email,
e.d_id d_id,d.id did,d.dept_name dept_name
FROM tbl_employee e JOIN tbl_dept d ON e.d_id=d.id
WHERE e.id=#{id}
</select>
結果:
Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=Department(id=1, departmentName=開發部, emps=null))
第二種封裝:使用association
定義關聯的單個對象的封裝規則;
EmployeeMapper.xml:
<resultMap id="MyEmp2" type="cn.codewhite.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
<!-- association可以指定聯合的javaBean對象
property="dept":指定哪個屬性是聯合的對象,這裏指定Employee中的dept
javaType:指定這個屬性對象的類型[不能省略],這裏聯合了Department
-->
<association property="dept" javaType="cn.codewhite.pojo.Department">
<!--同列名的話就不行,這裏的property="id"是Department下的id-->
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.email email,
e.d_id d_id,d.id did,d.dept_name dept_name
FROM tbl_employee e JOIN tbl_dept d ON e.d_id=d.id
WHERE e.id=#{id}
</select>
5.5.4:分步查詢:
方法:
1、先按照員工id查詢員工信息
2、根據查詢員工信息中的d_id值去部門表查出部門信息
3、部門設置到員工中;
分步查詢:
DepartmentMapper.xml:
<!--接口方法Department getDeptByIdStep(Integer id);-->
<select id="getDepdById" resultType="cn.codewhite.pojo.Department">
select id,dept_name departmentName from tbl_dept where id = #{id}
</select>
EmployeeMapper.xml:
<!-- id last_name email gender d_id -->
<resultMap id="MyEmpByStep" type="cn.codewhite.pojo.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!-- association定義關聯對象的封裝規則
select:表明當前屬性是調用select指定的方法查出的結果,這裏調用getDeptById()
column:指定將哪一列的值傳給這個方法,這裏使用指定用d_id列值傳入getDeptById方法裏
流程:使用select指定的方法(傳入column指定的這列參數的值)查出對象,並封裝給property指定的屬性
封裝給Employee的dept屬性
-->
<association property="dept" select="cn.codewhite.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</resultMap>
<select id="getEmpByIdStep" resultMap="MyEmpByStep">
select * from tbl_employee where id = #{id}
</select>
測試結果:
Employee empByIdStep = mapper.getEmpByIdStep(5);
結果:
Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=Department(id=1, departmentName=開發部, emps=null))
5.5.5:★關聯集合封裝
collection
標籤定義關聯的集合類型的屬性封裝規則:
collection
定義關聯集合類型的屬性的封裝規則, ofType`:指定集合裏面元素的類型
實現效果: 查詢指定id部門下的所有員工並且將員工封裝到員工集合中
DepartmentMapper:
Department getListByDeptId(Integer id);
DepartmentMapper.xml:
<!-- public class Department {
private Integer id;
private String departmentName;
private List<Employee> emps;
封裝javabean
did dept_name || emp_id emp_name gender email
}-->
<!--嵌套結果集的方式,使用collection標籤定義關聯的集合類型的屬性封裝規則 -->
<resultMap id="myList" type="cn.codewhite.pojo.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
<!--
collection定義關聯集合類型的屬性的封裝規則
ofType:指定集合裏面元素的類型
property="emps"===> private List<Employee> emps;
-->
<collection property="emps" ofType="cn.codewhite.pojo.Employee">
<id column="emp_id" property="id"/>
<result column="emp_name" property="lastName"/>
<result column="gender" property="gender"/>
<result column="email" property="email"/>
</collection>
</resultMap>
<select id="getListByDeptId" resultMap="myList">
SELECT d.id did,d.dept_name,e.id emp_id,
e.last_name emp_name,gender,email
FROM tbl_dept d
LEFT JOIN tbl_employee e ON d.id=e.d_id
WHERE d.id = #{id}
</select>
測試結果:getListByDeptId(1)
5.5.6:★一對多查詢(關聯集合分步查詢封裝):
實現需求: 查詢指定id部門下的所有員工並且將員工封裝到員工集合中,分兩步:
先查詢指定id的部門,然後查詢指定id部門下的所有員工
DepartmentMapper:
Department getDeptByIdStep(Integer id);
DepartmentMapper.xml: 在selcet中:column="id"的值就是傳入#{id}的值
<!-- collection:分段查詢 -->
<resultMap id="myStepMap" type="cn.codewhite.pojo.Department">
<id column="id" property="id"/>
<result column="dept_name" property="departmentName"/>
<collection property="emps" select="cn.codewhite.dao.EmployeeMapper.getEmpsListByDeptId"
column="id">
<!--這裏column可以寫成:colum=“{deptId=id}
在selcet中:column="id"的值就是傳入#{id}的值-->
</collection>
</resultMap>
<!-- public Department getDeptByIdStep(Integer id); -->
<select id="getDeptByIdStep" resultMap="myStepMap">
select id,dept_name from tbl_dept where id = #{id}
</select>
擴展:多列的值傳遞過去:
將多列的值封裝map傳遞;
column="{key1=column1,key2=column2}"
EmployeeMapper:
List<Employee> getEmpsListByDeptId(Integer deptId);
EmployeeMapper.xml
<!--查詢部門id員工-->
<!-- List<Employee> getEmpsListByDeptId(Integer deptId);-->
<select id="getEmpsListByDeptId" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where d_id = #{deptId}
</select>
測試結果:
getDeptByIdStep(1);
結果:
Department(id=1, departmentName=開發部, emps=[Employee(id=4, lastName=4號員工, email=codewhite@qq.com, gender=1, dept=null), Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=null), Employee(id=11, lastName=codewhite, email=codewhite@qq.com, gender=1, dept=null), Employee(id=13, lastName=新插入員工2, email=codewhite@qq.com, gender=0, dept=null), Employee(id=14, lastName=新插入員工3, email=codewhite@qq.com, gender=0, dept=null)])
我遇到的bug:
Error querying database. Cause: org.apache.ibatis.reflection.ReflectionException: Error instantiating interface cn.codewhite.dao.DepartmentMapper with invalid types () or values (). Cause: java.lang.NoSuchMethodException: cn.codewhite.dao.DepartmentMapper.<init>()
### The error may exist in cn/codewhite/dao/DepartmentMapper.xml
### The error may involve cn.codewhite.dao.DepartmentMapper.getDeptByIdStep
### The error occurred while handling results
### SQL: select id,dept_name from tbl_dept where id = ?
### Cause: org.apache.ibatis.reflection.ReflectionException: Error instantiating interface cn.codewhite.dao.DepartmentMapper with invalid types () or values (). Cause: java.lang.NoSuchMethodException: cn.codewhite.dao.DepartmentMapper.<init>()
由於我的在Mapper中結果集封裝寫錯了:
<resultMap type="cn.codewhite.pojo.Department" id="MyDeptStep">
type寫成了:cn.codewhite.dao.DepartmentMapper
5.5.7:延遲加載
可以使用延遲加載(懶加載);(按需加載)
fetchType=“lazy”:表示使用延遲加載;
lazy
:延遲eager
立即
Employee==>Dept:
我們每次查詢Employee對象的時候,都將一起查詢出來。
部門信息在我們使用的時候再去查詢;
分段查詢的基礎之上加上兩個配置:(全局配置文件中)
<!--lazyLoadingEnabled延遲加載的全局開關。當開啓時,所有關聯對象都會延遲加載。
特定關聯關係中可通過設置 fetchType 屬性來覆蓋該項的開關狀態。-->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 開啓時,任一方法的調用都會加載該對象的所有延遲加載屬性。
否則,每個延遲加載屬性會按需加載(參考 lazyLoadTriggerMethods)。-->
<setting name="aggressiveLazyLoading" value="false"/>
測試代碼:延遲加載是需要用到的時候再加載,立即加載是先加載執行所有sql語句
@Test
public void getListByDeptId() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
Department deptByIdStep = mapper.getDeptByIdStep(1);
//這裏只需要獲取指定id
System.out.println(deptByIdStep.getId());
//這裏需要獲取部門
System.out.println(deptByIdStep.getEmps());
} finally {
openSession.close();
}
}
5.6.1 鑑別器
<!-- <discriminator javaType=""></discriminator>
鑑別器:mybatis可以使用discriminator判斷某列的值,然後根據某列的值改變封裝行爲
封裝Employee:
如果查出的是女生:就把部門信息查詢出來,否則不查詢;
如果是男生,把last_name這一列的值賦值給email;
-->
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpDis">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--
column:指定判定的列名
javaType:列值對應的java類型 -->
<discriminator javaType="string" column="gender">
<!--女生 resultType:指定封裝的結果類型;不能缺少。/resultMap-->
<case value="0" resultType="com.atguigu.mybatis.bean.Employee">
<association property="dept"
select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById"
column="d_id">
</association>
</case>
<!--男生 ;如果是男生,把last_name這一列的值賦值給email; -->
<case value="1" resultType="com.atguigu.mybatis.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="last_name" property="email"/>
<result column="gender" property="gender"/>
</case>
</discriminator>
</resultMap>
第六章:★動態SQL
6.1.1 if判斷標籤與where標籤
test:判斷表達式(OGNL) 像JSTL表達式:c:if test
OGNL參照PPT或者官方文檔。
從參數中取值進行判斷,遇見特殊符號應該去寫轉義字符: &&:
查詢員工,要求,攜帶了哪個字段查詢條件就帶上這個字段的值
關係映射文件:
<!--接口方法:List<Employee> getEmpsByConditionIf(Employee employee);-->
<select id="getEmpsByConditionIf" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee where
<if test="id!=null">
id = #{id}
</if>
<if test="lastName!=null and lastName != "" ">
and last_name like #{lastName}
</if>
<if test="email!=null and email.trim()!=""">
and email=#{email}
</if>
<!-- ognl會進行字符串與數字的轉換判斷 "0"==0 -->
<if test="gender==0 or gender==1">
and gender=#{gender}
</if>
<!-- <if test="lastName!=null && lastName!=""">-->
<!-- and last_name like #{lastName}-->
<!-- </if>-->
</select>
測試:
@Test
public void testEmployeeMapperDynamicSQL() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
//會爲接口自動的創建一個代理對象,代理對象去執行增刪改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
List<Employee> empsByConditionIf = mapper.getEmpsByConditionIf(new Employee(6,"%e%", null, null, null));
empsByConditionIf.forEach(System.out::println);
} finally {
openSession.close();
}
}
結果:
問題一:上述情況,如果new Employee(null, "%e%", null, null, null)
,可以使用where與trim解決
id爲空那麼報錯:因爲sql語句拼接多了一個and
還有,判斷情況if下,test是形參的變量,別將lastName寫成了last_name
添加<where></where>
可以截取前面的and字符串。
使用new Employee(null, "%e%", null, null, null)
測試:此時即使id爲Null,也不會拼接多一個and再where的後邊
6.1.2 trim標籤
trim
字符串截取(where(封裝查詢條件), set(封裝修改條件))
prefix=""
:前綴:trim標籤體中是整個字符串拼串 後的結果。
prefix給拼串後的整個字符串加一個前綴
prefixOverrides=""
:
前綴覆蓋: 去掉整個字符串前面多餘的字符
suffix=""
:後綴
suffix給拼串後的整個字符串加一個後綴
suffixOverrides=""
後綴覆蓋:去掉整個字符串後面多餘的字符
在6.1.1中的if語句,還存在一個問題:若and寫在了sql語句的後邊,如:id=#{id} and,這個問題where是不能截取的,where只能截取前面的and,此時可以使用trim
來截取
解決方案:
mapper接口方法:<!--List<Employee> getEmpsByConditionTrim(Employee employee); -->
<select id="getEmpsByConditionTrim" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee
<!-- 自定義字符串的截取規則
prefix="where"在整體前綴加入where字符
suffixOverrides="and"後綴覆蓋:去掉整個字符串後面多餘的and
-->
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null && lastName!=""">
last_name like #{lastName} and
</if>
<if test="email!=null and email.trim()!=""">
email=#{email} and
</if>
<!-- ognl會進行字符串與數字的轉換判斷 "0"==0 -->
<if test="gender==0 or gender==1">
gender=#{gender}
</if>
</trim>
</select>
6.1.3 choose標籤
choose
(when, otherwise):分支選擇;帶了break的swtich-case,如果帶了id就用id查,如果帶了lastName就用lastName查;只會進入其中一個。
實現效果:如果帶了id就用id查,如果帶了lastName就用lastName查;只會進入其中一個
mapper接口:
List<Employee> getEmpsByConditionChoose(Employee employee);
關係映射文件:
<select id="getEmpsByConditionChoose" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee
<where>
<choose>
<when test="id!=null">
id=#{id}
</when>
<when test="lastName!=null">
last_name like #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<otherwise>
gender = 0
</otherwise>
</choose>
</where>
</select>
測試代碼:這裏我只帶了id,所以是根據id查詢
@Test
public void getEmpsByConditionChoose() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
List<Employee> choose = mapper.getEmpsByConditionChoose(new Employee(5, null, null, 1, null));
System.out.println(choose);
} finally {
openSession.close();
}
}
結果:[Employee(id=5, lastName=xiaobai, email=xiaobai@qq.com, gender=1, dept=null)]
6.1.4 set標籤
實現需求:修改指定id的員工
EmployeeMapper方法:
void updateEmp(Employee employee);
EmployeeMapper.xml:
<!--public void updateEmp(Employee employee); -->
<update id="updateEmp">
update tbl_employee
<!-- set可以截取後邊的逗號 “,”-->
<set>
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender},
</if>
</set>
where id=#{id}
</update>
測試結果:
如果沒有使用<set></set>
標籤,那麼會報錯,因爲sql語句後面的逗號沒有截取,發送的語句:
update tbl_employee set email=?, gender=?, where id=?
加入了set標籤後,就會截取多餘的逗號。發送的語句:
update tbl_employee SET email=?, gender=? where id=?
第二種修改方法:這樣也能達到效果,使用Trim
標籤
<update>
<!--
Trim:更新拼串
update tbl_employee
prefix="set":整體的前綴加一個set
suffixOverrides=""後綴覆蓋:去掉整個字符串後面多餘的字符
suffixOverrides=","如果多出了“,”那麼截取 -->
<trim prefix="set" suffixOverrides=",">
<if test="lastName!=null">
last_name=#{lastName},
</if>
<if test="email!=null">
email=#{email},
</if>
<if test="gender!=null">
gender=#{gender}
</if>
</trim>
where id=#{id}
</update>
6.1.5 foreach標籤與批量插入
前提條件:mysql子查詢複習
一條查詢語句中又嵌套了另一條完整的select語句,其中被嵌套的select語句,稱爲子查詢或內查詢,在外面的查詢語句,稱爲主查詢或外查詢
特點:
1、子查詢都放在小括號內
2、子查詢可以放在from後面、select後面、where後面、having後面,但一般放在條件的右側
3、子查詢優先於主查詢執行,主查詢使用了子查詢的執行結果
4、子查詢根據查詢結果的行數不同分爲以下兩類:
① 單行子查詢
結果集只有一行
一般搭配單行操作符使用:> < = <> >= <=
非法使用子查詢的情況:
a、子查詢的結果爲一組值
b、子查詢的結果爲空
② 多行子查詢
結果集有多行
一般搭配多行操作符使用:any、all、in、not in
in: 屬於子查詢結果中的任意一個就行
any和all往往可以用其他查詢代替
foreach
標籤:遍歷集合
要求,查詢任意一個員工,添加到集合中:
EmployeeMapper接口:千萬不要在接口中少了@Param("ids")
這個註解否則報以下異常:
List<Employee> getEmpsByConditionForeach(@Param("ids")List<Integer> ids);
異常:
Error querying database. Cause: org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [collection, list]
Cause: org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [collection, list]
EmployeeMapper.xml:
<!--public List<Employee> getEmpsByConditionForeach(List<Integer> ids); -->
<select id="getEmpsByConditionForeach" resultType="cn.codewhite.pojo.Employee">
select * from tbl_employee
<!--
要求:獲取員工,根據集合遍歷的id 原sql語句:select * from tbl_employee where id in(1,2,3)
collection:指定要遍歷的集合:這裏遍歷:ids,需要註解!
list類型的參數會特殊處理封裝在map中,map的key就叫list
item:將當前遍歷出的元素賦值給指定的變量
separator:每個元素之間的分隔符,如果不寫這個的話,在#{變量名}後邊加“,”那麼會報錯。
open:遍歷出所有結果拼接一個開始的字符
close:遍歷出所有結果拼接一個結束的字符
index:索引。遍歷list的時候是index就是索引,item就是當前值
遍歷map的時候index表示的就是map的key,item就是map的值
#{變量名}就能取出變量的值也就是當前遍歷出的元素
-->
<foreach collection="ids" item="item_id" separator=","
open="where id in(" close=")">
#{item_id}
</foreach>
</select>
測試代碼:
@Test
public void testgetEmpsByConditionForeach() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
// 3、獲取接口的實現類對象
//會爲接口自動的創建一個代理對象,代理對象去執行增刪改查方法
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
List empsByConditionForeach = mapper.getEmpsByConditionForeach(Arrays.asList(5,6,8));
empsByConditionForeach.forEach(System.out::println);
} finally {
openSession.close();
}
}
結果:
6.1.6 抽取可重複sql的值
抽取可重用的sql片段。方便後面引用
1、sql抽取:經常將要查詢的列名,或者插入用的列名抽取出來方便引用
2、include來引用已經抽取的sql:
3、include還可以自定義一些property,sql標籤內部就能使用自定義的屬性
include-property:取值的正確方式${prop}, #{不能使用這種方式}
mapper映射文件:
<insert id="addEmp" parameterType="cn.codewhite.pojo.Employee"
useGeneratedKeys="true" keyProperty="id">
insert into tbl_employee(
<!--引用sql片段-->
<include refid="insertColumn">
<property name="testColomn" value="abc"/>
</include>
<!-- 原有字段last_name,email,gender,d_id-->
)
values (#{lastName},#{email},#{gender})
</insert>
<!--sql抽取-->
<sql id="insertColumn">
<if test="_databaseId=='mysql'">
last_name,email,gender
</if>
</sql>
取值用法:在include中定義的屬性使用${屬性}取值。
<include refid="insertColumn">
<property name="testColomn" value="abc"/>
</include>
<sql id="insertColumn">
<if test="_databaseId=='mysql'">
last_name,email,gender,d_id,${testColomn}
</if>
</sql>
6.1.7 內置參數:
不只是方法傳遞過來的參數,可以用來判斷,取值
第一個:_parameter
:代表整個參數
單個參數:_parameter就是這個參數
多個參數:參數會封裝成一個map:_parameter
:就是整個參數
`_databaseId`:如果配置了`databaseIdProvider`標籤
那麼_databaseId
就是代表當前數據庫的別名這裏我配置了是mysql
xml配置文件:如果是mysql就執行mysql的sql語句
<!--接口方法:List<Employee> queryTestParamAndDatabaseId(Employee e);-->
<select id="queryTestParamAndDatabaseId" resultType="cn.codewhite.pojo.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
<if test="_parameter!=null">
<!-- #{lastName}取出的是employee的lastName的值-->
where last_name = #{lastName}
<!--where last_name = #{_parameter.lastName}-->
</if>
</if>
<if test="_databaseId=='oracle'">
select * from tbl_employee
</if>
</select>
6.1.8 bink標籤
bind
:可以將OGNL表達式的值綁定到一個變量中,方便後來引用這個變量的值
如:<bind name="_lastName" value="'%'+lastName+'%'"/>
:綁定了之後,就可以用#{_lastName}引用綁定後的變量值了
<!-- #{lastName}取出的是employee的lastName的值-->
<!--List<Employee> queryTestParamAndDatabaseId(Employee e);-->
<select id="queryTestParamAndDatabaseId" resultType="cn.codewhite.pojo.Employee">
<if test="_databaseId=='mysql'">
select * from tbl_employee
<!-- bind:可以將OGNL表達式的值綁定到一個變量中,方便後來引用這個變量的值 -->
<bind name="_lastName" value="'%'+lastName+'%'"/>
<if test="_parameter!=null">
where last_name like #{lastName}
<!-- 可以將#{lastName}改成自定義的#{_lastName}就不用在模糊查詢時的參數輸入%符號了 -->
</if>
</if>
第七章:★Mybatis緩存
7.1.1 一級緩存:
(本地緩存):sqlSession級別的緩存。一級緩存是一直開啓的;SqlSession級別的一個Map。
與數據庫同一次會話期間查詢到的數據會放在本地緩存中。
以後如果需要獲取相同的數據,直接從緩存中拿,沒必要再去查詢數據庫;
一級緩存失效情況(沒有使用到當前一級緩存的情況,效果就是,還需要再向數據庫發出查詢):
1、sqlSession不同。
2、sqlSession相同,查詢條件不同.(當前一級緩存中還沒有這個數據)
3、sqlSession相同,兩次查詢之間執行了增刪改操作(這次增刪改可能對當前數據有影響)
4、sqlSession相同,手動清除了一級緩存(緩存清空)
測試代碼:
@Test
public void testFirstLevelCache() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Employee emp01 = mapper.getEmpById(1);
System.out.println(emp01);
//xxxxx
//1、sqlSession不同。
//SqlSession openSession2 = sqlSessionFactory.openSession();
//EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
//2、sqlSession相同,查詢條件不同
//3、sqlSession相同,兩次查詢之間執行了增刪改操作(這次增刪改可能對當前數據有影響)
//mapper.addEmp(new Employee(null, "testCache", "cache", "1"));
//System.out.println("數據添加成功");
//4、sqlSession相同,手動清除了一級緩存(緩存清空)
//openSession.clearCache();
Employee emp02 = mapper.getEmpById(1);
//Employee emp03 = mapper.getEmpById(3);
System.out.println(emp02);
//System.out.println(emp03);
System.out.println(emp01==emp02);
//openSession2.close();
}finally{
openSession.close();
}
}
7.1.2 二級緩存
(全局緩存):基於namespace級別的緩存:一個namespace對應一個二級緩存:
工作機制:
1、一個會話,查詢一條數據,這個數據就會被放在當前會話的一級緩存中;
2、如果會話關閉;一級緩存中的數據會被保存到二級緩存中;新的會話查詢信息,就可以參照二級緩存中的內容;
3、sqlSession=EmployeeMapper>Employee
DepartmentMapper===>Department
不同namespace查出的數據會放在自己對應的緩存中(map)
效果:數據會從二級緩存中獲取
查出的數據都會被默認先放在一級緩存中。
只有會話提交或者關閉以後,一級緩存中的數據纔會轉移到二級緩存中
使用:
1)、開啓全局二級緩存配置:<setting name="cacheEnabled" value="true"/>
2)、去mapper.xml中配置使用二級緩存:
<cache></cache>
3)、我們的POJO需要實現序列化接口
全局config配置:
EmployeeMapper.xml二級緩存:
<cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024"></cache>
eviction:緩存的回收策略:
• LRU
– 最近最少使用的:移除最長時間不被使用的對象。
• FIFO
– 先進先出:按對象進入緩存的順序來移除它們。
• SOFT
– 軟引用:移除基於垃圾回收器狀態和軟引用規則的對象。
•WEAK
– 弱引用:更積極地移除基於垃圾收集器狀態和弱引用規則的對象。
• 默認的是 LRU
。
flushInterval
:緩存刷新間隔:緩存多長時間清空一次,默認不清空,設置一個毫秒值
readOnly
:是否只讀:
true
:只讀;mybatis認爲所有從緩存中獲取數據的操作都是隻讀操作,不會修改數據。
mybatis爲了加快獲取速度,直接就會將數據在緩存中的引用交給用戶。不安全,速度快
false
:非只讀:mybatis覺得獲取的數據可能會被修改。
mybatis會利用序列化&反序列的技術克隆一份新的數據給你。安全,速度慢
size
:緩存存放多少元素;
type
="":指定自定義緩存的全類名;
實現Cache接口即可,Cache接口:
這裏我EmployeeMapper.xml二級緩存使用默認配置:
測試代碼:
@Test
public void testCache() {
SqlSession sqlSession = null;
try {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
Employee emp1 = mapper.getEmpById(5);
System.out.println(emp1);
//必須關閉會話
sqlSession.close();
sqlSession.clearCache();
SqlSession sqlSession1 = sqlSessionFactory.openSession();
EmployeeMapper mapper1 = sqlSession1.getMapper(EmployeeMapper.class);
Employee emp2 = mapper1.getEmpById(5);
System.out.println(emp2);
System.out.println(emp1 == emp2);
} catch (IOException e) {
e.printStackTrace();
}
}
測試結果:
可以看到,未開啓二級緩存的時候,發出兩個sql,開啓了後,只發出一個sql,他們是不同會話的!
二級緩存其他配置:
和緩存有關的設置/屬性:
1)cacheEnabled=true
:false:關閉緩存(二級緩存關閉)(一級緩存一直可用的)
全局配置文件中:
2)、每個select標籤都有useCache="true"
:false:不使用緩存(一級緩存依然使用,二級緩存不使用),默認true
3)、【每個增刪改標籤的:flushCache=“true”:(一級二級都會清除)】
增刪改執行完成後就會清楚緩存;
測試:flushCache=“true”:一級緩存就清空了;二級也會被清除;
查詢標籤:flushCache="false"
:
如果flushCache=true
;每次查詢之後都會清空緩存;緩存是沒有被使用的;
4)、sqlSession.clearCache();
只是清除當前session的一級緩存;
5)、localCacheScope
:本地緩存作用域:(一級緩存SESSION);當前會話的所有數據保存在會話緩存中;
STATEMENT:可以禁用一級緩存;
7.1.3 自定義緩存
1.導入第三方緩存包即可;
2.導入與第三方緩存整合的適配包;官方有;
3.mapper.xml中使用自定義緩存
<cachetype="org.mybatis.caches.ehcache.EhcacheCache"></cache>
ehcache.xml:
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../config/day05_mybatis_cache>
<!-- 磁盤保存路徑 -->
<diskStore path="D:\44\ehcache" />
<defaultCache
maxElementsInMemory="10000"
maxElementsOnDisk="10000000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>
</ehcache>
<!--
屬性說明:
l diskStore:指定數據在磁盤中的存儲位置。
l defaultCache:當藉助CacheManager.add("demoCache")創建Cache時,EhCache便會採用<defalutCache/>指定的的管理策略
以下屬性是必須的:
l maxElementsInMemory - 在內存中緩存的element的最大數目
l maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大
l eternal - 設定緩存的elements是否永遠不過期。如果爲true,則緩存的數據始終有效,如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷
l overflowToDisk - 設定當內存緩存溢出的時候是否將過期的element緩存到磁盤上
以下屬性是可選的:
l timeToIdleSeconds - 當緩存在EhCache中的數據前後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閒置時間無窮大
l timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每個Cache都應該有自己的一個緩衝區.
l diskPersistent - 在VM重啓的時候是否啓用磁盤保存EhCache中的數據,默認是false。
l diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數據的清理工作
l memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
-->
第八章:★Mybatis插件開發
8.1.1 generator插件
依賴導入:
<!--mgb逆向工程-->
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.7</version>
</dependency>
mgb.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--
targetRuntime="MyBatis3Simple":生成簡單版的CRUD
MyBatis3:豪華版
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- jdbcConnection:指定如何連接到目標數據庫 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/eesy_mybatis?allowMultiQueries=true"
userId="root"
password="123">
</jdbcConnection>
<!-- -->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- javaModelGenerator:指定javaBean的生成策略
targetPackage="test.model":目標包名
targetProject="\MBGTestProject\src":目標工程
-->
<javaModelGenerator targetPackage="cn.codewhite.pojo"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- sqlMapGenerator:sql映射生成策略: -->
<sqlMapGenerator targetPackage="cn.codewhite.dao"
targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- javaClientGenerator:指定mapper接口所在的位置 -->
<javaClientGenerator type="XMLMAPPER" targetPackage="cn.codewhite.dao"
targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 指定要逆向分析哪些表:根據表要創建javaBean -->
<table tableName="tbl_dept" domainObjectName="Department"></table>
<table tableName="tbl_employee" domainObjectName="Employee"></table>
</context>
</generatorConfiguration>
創建mgb工程代碼:
@Test
public void testMbg() throws Exception {
List<String> warnings = new ArrayList<String>();
boolean overwrite = true;
File configFile = new File("src/main/resources/mbg.xml");
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(configFile);
DefaultShellCallback callback = new DefaultShellCallback(overwrite);
MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
callback, warnings);
myBatisGenerator.generate(null);
}
一鍵生成成功:
測試代碼:
@Test
public void selectAll() throws IOException {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = mapper.selectAll();
employees.forEach(System.out::println);
sqlSession.close();
}
結果:
拼裝SQL測試:
@Test
public void testmyBatis3() {
SqlSession sqlSession = null;
try {
sqlSession = getSqlSessionFactory().openSession();
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//xxxExample就是封裝查詢條件的
//1、查詢所有
//List<Employee> emps = mapper.selectByExample(null);
//2、查詢員工名字中有e字母的,和員工性別是1的
//封裝員工查詢條件的example
EmployeeExample example = new EmployeeExample();
//創建一個Criteria,這個Criteria就是拼裝查詢條件
//select id, last_name, email, gender, d_id from tbl_employee
//WHERE ( last_name like ? and gender = ? ) or email like "%e%"
EmployeeExample.Criteria criteria = example.createCriteria();
criteria.andLastNameLike("%e%");
criteria.andGenderEqualTo("1");
List<Employee> employees = mapper.selectByExample(example);
employees.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
} finally {
assert sqlSession != null;
sqlSession.close();
}
結果:
8.1.2 page插件
官方文檔:page官方文檔
依賴導入:
<!--mybatis: page插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.2</version>
</dependency>
EmployeeMapper:
List<Employee> getEmp();
EmployeeMapper.xml:
<select id="getEmp" resultType="cn.codewhite.pojo.Employee">
select id,last_name,gender,email from tbl_employee
</select>
測試代碼:
@Test
public void test01() throws IOException {
// 1、獲取sqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
// 2、獲取sqlSession對象
SqlSession openSession = sqlSessionFactory.openSession();
try {
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
Page<Object> page = PageHelper.startPage(5, 1);
List<Employee> emps = mapper.getEmp();
//傳入要連續顯示多少頁
PageInfo<Employee> info = new PageInfo<>(emps, 5);
for (Employee employee : emps) {
System.out.println(employee);
}
/*System.out.println("當前頁碼:"+page.getPageNum());
System.out.println("總記錄數:"+page.getTotal());
System.out.println("每頁的記錄數:"+page.getPageSize());
System.out.println("總頁碼:"+page.getPages());*/
///xxx
System.out.println("當前頁碼:" + info.getPageNum());
System.out.println("總記錄數:" + info.getTotal());
System.out.println("每頁的記錄數:" + info.getPageSize());
System.out.println("總頁碼:" + info.getPages());
System.out.println("是否第一頁:" + info.isIsFirstPage());
System.out.println("連續顯示的頁碼:");
int[] nums = info.getNavigatepageNums();
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
//xxxx
} finally {
openSession.close();
}
}
8.1.3 批量插入插件
ExecutorType.BATCH
@Test
public void testBatch() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
//可以執行批量操作的sqlSession
SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
long start = System.currentTimeMillis();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
for (int i = 0; i < 10000; i++) {
mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0, 5), "b", "1"));
}
openSession.commit();
long end = System.currentTimeMillis();
//批量:(預編譯sql一次==>設置參數===>10000次===>執行(1次))
//Parameters: 616c1(String), b(String), 1(String)==>4598
//非批量:(預編譯sql=設置參數=執行)==》10000 10200
System.out.println("執行時長:"+(end-start));
}finally{
openSession.close();
}
}
整合Spring:
<!--配置一個可以進行批量執行的sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactoryBean"></constructor-arg>
<constructor-arg name="executorType" value="BATCH"></constructor-arg>
</bean>
8.1.4 插件開發
插件原理:在四大對象創建的時候
1、每個創建出來的對象不是直接返回的,而是
interceptorChain.pluginAll(parameterHandler);
2、獲取到所有的Interceptor(攔截器)(插件需要實現的接口);
調用interceptor.plugin(target);返回target包裝後的對象
3、插件機制,我們可以使用插件爲目標對象創建一個代理對象;AOP(面向切面)
我們的插件可以爲四大對象創建出代理對象;
代理對象就可以攔截到四大對象的每一個執行;
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
插件編寫:
- 編寫Interceptor的實現類
- 使用@Intercepts註解完成插件簽名
- 將寫好的插件註冊到全局配置文件中
MyFirstPlugin:
/**
* 完成插件簽名:
* 告訴MyBatis當前插件用來攔截哪個對象的哪個方法
*/
@Intercepts(
{ @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor{
/**
* intercept:攔截:
* 攔截目標對象的目標方法的執行;
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
// TODO Auto-generated method stub
System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
//動態的改變一下sql運行的參數:以前1號員工,實際從數據庫查詢11號員工
Object target = invocation.getTarget();
System.out.println("當前攔截到的對象:"+target);
//拿到:StatementHandler==>ParameterHandler===>parameterObject
//拿到target的元數據
MetaObject metaObject = SystemMetaObject.forObject(target);
Object value = metaObject.getValue("parameterHandler.parameterObject");
System.out.println("sql語句用的參數是:"+value);
//修改完sql語句要用的參數
metaObject.setValue("parameterHandler.parameterObject", 11);
//執行目標方法
Object proceed = invocation.proceed();
//返回執行後的返回值
return proceed;
}
/**
* plugin:
* 包裝目標對象的:包裝:爲目標對象創建一個代理對象
*/
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
//我們可以藉助Plugin的wrap方法來使用當前Interceptor包裝我們目標對象
System.out.println("MyFirstPlugin...plugin:mybatis將要包裝的對象"+target);
Object wrap = Plugin.wrap(target, this);
//返回爲當前target創建的動態代理
return wrap;
}
/**
* setProperties:
* 將插件註冊時 的property屬性設置進來
*/
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
System.out.println("插件配置的信息:"+properties);
}
}
MySecondPlugin :
@Intercepts(
{ @Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
})
public class MySecondPlugin implements Interceptor{
@Override
public Object intercept(Invocation invocation) throws Throwable {
System.out.println("MySecondPlugin...intercept:"+invocation.getMethod());
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
// TODO Auto-generated method stub
System.out.println("MySecondPlugin...plugin:"+target);
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
全局配置文件註冊插件:
<!--plugins:註冊插件 -->
<plugins>
<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
<property name="username" value="root"/>
<property name="password" value="123456"/>
</plugin>
<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin"></plugin>
</plugins>
第九章: 拓展功能
9.1.1 Mybatis調用存儲過程
Oracle:創建帶遊標的儲存過程
CREATE OR REPLACE PROCEDURE
hello_test(
p_start IN INT,p_end IN INT,p_count OUT INT,
p_emps OUT sys_refcursor
)AS
BEGIN
SELECT count(*) INTO p_count FROM employees;
OPEN p_emps FOR
SELECT * FROM (SELECT rownum rn,e.* FROM employees e
WHERE rownum <= p_end)
WHERE rn>=p_start;
END hello_test;
關係映射文件:
<!-- void getPageByProcedure();
1、使用select標籤定義調用存儲過程
2、statementType="CALLABLE":表示要調用存儲過程
3、{call procedure_name(params)}
-->
<select id="getPageByProcedure" statementType="CALLABLE" databaseId="oracle">
{call hello_test(
#{start,mode=IN,jdbcType=INTEGER},
#{end,mode=IN,jdbcType=INTEGER},
#{count,mode=OUT,jdbcType=INTEGER},
#{emps,mode=OUT,jdbcType=CURSOR,javaType=ResultSet,resultMap=PageEmp}
)}
</select>
<resultMap type="cn.codewhite.pojo.Employee" id="PageEmp">
<id column="EMPLOYEE_ID" property="id"/>
<result column="LAST_NAME" property="email"/>
<result column="EMAIL" property="email"/>
</resultMap>
OraclePage:
@Data
public class OraclePage {
private int start;
private int end;
private int count;
private List<Employee> emps;
}
測試代碼:
/**
* oracle分頁:
* 藉助rownum:行號;子查詢;
* 存儲過程包裝分頁邏輯
* @throws IOException
*/
@Test
public void testProcedure() throws IOException{
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession openSession = sqlSessionFactory.openSession();
try{
EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
OraclePage page = new OraclePage();
page.setStart(1);
page.setEnd(5);
mapper.getPageByProcedure(page);
System.out.println("總記錄數:"+page.getCount());
System.out.println("查出的數據:"+page.getEmps().size());
System.out.println("查出的數據:"+page.getEmps());
}finally{
openSession.close();
}
}
9.1.2 自定義類型處理器
環境搭建:sql
ALTER TABLE tbl_employee ADD empStatus INT;
EmpStaus:枚舉類
/**
* 希望數據庫保存的是100,200這些狀態碼,而不是默認0,1或者枚舉的名
* @create 2020-05-22 9:53
*/
public enum EmpStatus {
LOGIN(100, "用戶登陸"), LOGOUT(200, "用戶登出"), REMOVE(300, "用戶不存在");
private Integer code;
private String msg;
private EmpStatus(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
//getter,setter自行添加
//按照狀態碼返回枚舉對象
public static EmpStatus getEmpStatusByCode(Integer code){
switch (code){
case 100:
return LOGIN;
case 200:
return LOGOUT;
case 300:
return REMOVE;
default:
return LOGOUT;
}
}
}
Employee:
@Data
public class Employee implements Serializable {
private Integer id;
private String lastName;
private String email;
private Integer gender;
//員工狀態:
private EmpStatus empStatus = EmpStatus.LOGOUT;
}
自定義的類型處理器:
/**
* 1、實現TypeHandler接口。或者繼承BaseTypeHandler
* @author JUNSHI
* @create 2020-05-22 10:01
*/
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus> {
/**
* 定義當前數據如何保存到數據庫中
*/
@Override
public void setParameter(PreparedStatement ps, int i, EmpStatus parameter, JdbcType jdbcType) throws SQLException {
System.out.println("要保存的狀態碼:"+parameter.getCode());
ps.setObject(i,parameter.getCode());
}
@Override
public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException {
//需要根據從數據庫中拿到的枚舉的狀態碼返回一個枚舉對象
int code = rs.getInt(columnName);
System.out.println("從數據庫中獲取的狀態碼:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(ResultSet rs, int columnIndex) throws SQLException {
int code = rs.getInt(columnIndex);
System.out.println("從數據庫中獲取的狀態碼:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
@Override
public EmpStatus getResult(CallableStatement cs, int columnIndex) throws SQLException {
int code = cs.getInt(columnIndex);
System.out.println("從數據庫中獲取的狀態碼:"+code);
EmpStatus status = EmpStatus.getEmpStatusByCode(code);
return status;
}
}
全局配置文件開啓:
<typeHandlers>
<!--1、配置我們自定義的TypeHandler -->
<typeHandler handler="cn.codewhite.pojo.MyEnumEmpStatusTypeHandler" javaType="cn.codewhite.pojo.EmpStatus"/>
<!--2、也可以在處理某個字段的時候告訴MyBatis用什麼類型處理器
保存:#{empStatus,typeHandler=xxxx}
查詢:
<resultMap type="cn.codewhite.pojo.Employee" id="MyEmp">
<id column="id" property="id"/>
<result column="empStatus" property="empStatus" typeHandler=""/>
</resultMap>
注意:如果在參數位置修改TypeHandler,應該保證保存數據和查詢數據用的TypeHandler是一樣的。
-->
</typeHandlers>
寫在後邊:
寫得不好,若有任何問題可以找我。
以上我學習的資料來源於:百度百科,官方文檔,黑馬,尚硅谷等