Mybatis輕量化框架學習

寫在前面: Mybatis是一個優秀的框架,今天我給大家介紹一下mybatis的用法,本文特別長,可以和官方文檔一起食用。並且我這裏整理了一份PDF格式的筆記,需要可以找我,全文8W字符,寫得很累,希望能給個贊。
公衆號:小白編碼。

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程序編寫步驟

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VuvNEAfB-1590116276174)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200520230424532.png)]

補充: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

在不使用別名的情況下,沒有開啓駝峯命名它就無法封裝信息:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ZsIF9wFa-1590116276176)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200515123534188.png)]

如何開啓:全局配置文件中加入settings配置:mapUnderscoreToCamelCasetrue

<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中定義的都是別名

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ENcVKD2c-1590116276177)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200521120539838.png)]

<!--配置環境,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同一個包的概念:

下圖這樣屬於同一個包。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-u0dXuZkr-1590116276178)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200521131127862.png)]

在Maven工程中:其實這兩個也是算同一個包內:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-RcVvjlvK-1590116276179)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200521131352892.png)]

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後:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ybWQFxnx-1590116276181)()]

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}

#{}:更豐富的用法:
規定參數的一些規則:
javaTypejdbcTypemode(存儲過程)numericScale、
resultMaptypeHandlerjdbcTypeNameexpression(未來準備支持的功能);

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、要求多條查詢語句的查詢的每一列的類型和順序最好一致
3union關鍵字默認去重,如果使用union all 可以包含重複項

連接查詢:

語法:
	select 查詢列表
	from1 別名 【連接類型】
	join2 別名 
	on 連接條件
	【where 篩選條件】
	【group by 分組】
	【having 篩選條件】
	【order by 排序列表】
	

分類:
內連接(★):inner
外連接
	左外():leftouter】
	右外()rightouter】
	全外:fullouter】
交叉連接: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:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4PSRcWP3-1590116276182)()]

tbl_dept:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-LvJf5Gbf-1590116276183)()]

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)

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cx7OiYW7-1590116276184)()]

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立即

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x8Cu1zLP-1590116276185)(]

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();
    }
}

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-w9q9ZzVm-1590116276186)()][外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-80DNru7y-1590116276186)()]

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 != &quot;&quot; ">
            and last_name like #{lastName}
        </if>
        <if test="email!=null and email.trim()!=&quot;&quot;">
            and email=#{email}
        </if>
        <!-- ognl會進行字符串與數字的轉換判斷  "0"==0 -->
        <if test="gender==0 or gender==1">
            and gender=#{gender}
        </if>
        <!--        <if test="lastName!=null &amp;&amp; lastName!=&quot;&quot;">-->
        <!--            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();
        }
    } 

結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-xIhR2tJF-1590116276187)()]

問題一:上述情況,如果new Employee(null, "%e%", null, null, null),可以使用where與trim解決

id爲空那麼報錯:因爲sql語句拼接多了一個and
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-9ruhFbLy-1590116276188)()]

還有,判斷情況if下,test是形參的變量,別將lastName寫成了last_name

添加<where></where>可以截取前面的and字符串。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-VeI9DudE-1590116276189)(
使用new Employee(null, "%e%", null, null, null)測試:此時即使id爲Null,也不會拼接多一個and再where的後邊

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bciyqIyN-1590116276190)(]

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來截取

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-UtAnHNZX-1590116276191)(]

解決方案:

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 &amp;&amp; lastName!=&quot;&quot;">
            last_name like #{lastName} and
        </if>
        <if test="email!=null and email.trim()!=&quot;&quot;">
            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、子查詢的結果爲空
	
② 多行子查詢
	結果集有多行
	一般搭配多行操作符使用:anyallinnot in
	in: 屬於子查詢結果中的任意一個就行
	anyall往往可以用其他查詢代替

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();
        }
    }

結果:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-hlI9j586-1590116276192)()]

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 二級緩存

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tF80Q5JT-1590116276192)(]

(全局緩存):基於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配置:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-WdUrzoKi-1590116276193)()]

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接口:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-GXw80KnK-1590116276194)()]這裏我EmployeeMapper.xml二級緩存使用默認配置:
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-DfA5uAXg-1590116276195)()]

測試代碼:

  @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();
        }
    }

測試結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-diuP6NKi-1590116276195)()]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vdn1fF6k-1590116276196)()]

可以看到,未開啓二級緩存的時候,發出兩個sql,開啓了後,只發出一個sql,他們是不同會話的!

二級緩存其他配置:

和緩存有關的設置/屬性:

1)cacheEnabled=true:false:關閉緩存(二級緩存關閉)(一級緩存一直可用的)

全局配置文件中:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-aeEwNDLP-1590116276197)(]
​ 2)、每個select標籤都有useCache="true":false:不使用緩存(一級緩存依然使用,二級緩存不使用),默認true

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-4HEVVsnP-1590116276197)()]

​ 3)、【每個增刪改標籤的:flushCache=“true”:(一級二級都會清除)】
​ 增刪改執行完成後就會清楚緩存;
​ 測試:flushCache=“true”:一級緩存就清空了;二級也會被清除;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-ff87OiuJ-1590116276198)()]

查詢標籤:flushCache="false"
如果flushCache=true;每次查詢之後都會清空緩存;緩存是沒有被使用的;
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-G56FBFwc-1590116276199)()]

4)、sqlSession.clearCache();只是清除當前session的一級緩存;

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7epPSiyR-1590116276200)()]

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);
}

一鍵生成成功:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-OEBRNOjS-1590116276201)()]

測試代碼:

  @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();
    }

結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-bfsjIazH-1590116276202)(]

拼裝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();
    }

結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-Bfyn70KZ-1590116276203)()]

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 插件開發

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-3JQqbrgj-1590116276204)(C:\Users\JUN\AppData\Roaming\Typora\typora-user-images\image-20200520212921536.png)]

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-cBZ2a5Y8-1590116276205)()]

插件原理:在四大對象創建的時候
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>

寫在後邊:

寫得不好,若有任何問題可以找我。
以上我學習的資料來源於:百度百科,官方文檔,黑馬,尚硅谷等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章