主頁 > 後端開發 > 沿著哈勃望遠,看清MyBatis整體架構

沿著哈勃望遠,看清MyBatis整體架構

2021-10-09 08:26:00 後端開發

文章目錄

  • 一、前言
  • 二、MyBatis四層架構
    • 2.1 介面層
      • 2.1.1 MyBatis使用Statement ID與資料庫互動
      • 2.1.2 Mybatis使用Mapper介面與資料庫互動
    • 2.2 資料處理層
      • 2.2.1 引數映射
      • 2.2.2 動態SQL陳述句生成
      • 2.2.3 SQL陳述句的執行
      • 2.2.4 結果處理
    • 2.3 框架支撐層
      • 2.3.1 事務管理機制
      • 2.3.2 連接池管理機制
      • 2.3.3 快取機制
      • 2.3.4 SQL陳述句的配置方式
    • 2.4 引導層
  • 三、MyBatis的主要構件及其相互關系
  • 四、從MyBatis一次select 查詢陳述句來分析MyBatis的架構設計
    • 4.1 從宏觀的角度看
      • 4.1.1 資料庫資料準備
      • 4.1.2 Mybatis組態檔
      • 4.1.3 引數映射
      • 4.1.4 相關依賴
      • 4.1.5 客戶端代碼
    • 4.2 從原始碼的角度看mybatis查詢
      • 4.2.1 開啟一個資料庫訪問會話
      • 4.2.2 SqlSession組件
      • 4.2.3 Executor執行器
      • 4.2.4 StatementHandler與Statement
        • 4.2.4.1 設定引數
        • 4.2.4.2 查詢
      • 4.2.5 小結
  • 五、尾聲

一、前言

MyBatis是目前非常流行的ORM框架,它的功能很強大,然而其實作卻比較簡單、優雅,本文主要講述MyBatis的架構設計思路,并且討論MyBatis的幾個核心部件,然后結合一個select查詢實體,深入代碼,來探究MyBatis的實作,

二、MyBatis四層架構

MyBatis從上到下分為介面層、資料處理層、框架支撐層、引導層,如下:

在這里插入圖片描述

介面層作用:介面層用于接收請求,呼叫sql陳述句,
介面層詳細:Mybatis介面層提供兩種資料庫訪問方式,Mybatis Statement ID訪問資料庫和Mapper介面訪問資料庫,底層都是一樣的,都是新建SqlSession物件,并向其傳入statementId和引數,最后使用SqlSession調動sql陳述句;

資料處理層作用:處理資料,
資料處理包括四個步驟:
(1) 引數映射(查詢階段,java型別的資料轉換成jdbc型別的資料,通過 preparedStatement.setXXX() 來設值;回傳階段,resultset查詢結果集中的 jdbcType 型別的資料轉換成java型別資料);
(2) sql陳述句動態生成;
(3) sql陳述句執行;
(4) 回傳值處理(對于回傳的結果集,支持結果集關系一對多和多對一的轉換,并且有兩種支持方式,一種為 嵌套查詢陳述句的查詢,還有一種是 嵌套結果集的查詢,)

框架支撐層作用:在資料處理層下面,為資料處理提供技術支持,
技術性支持包括四個:快取、事務、資料源與連接池、兩種配置方式的支持(xml配置和注解+注解驅動),其中,快取包括一級快取和二級快取,用于select查詢加速,update/delete/insert快取即清空;事務保證一致性;連接池實作Collection連接復用;

引導層作用:位于最下面,用于引導整個mybatis啟動與運行,
引導層包括兩種方式:xml配置方式和Java api方式,

2.1 介面層

介面層提供MyBatis和資料庫的互動兩種方式:

a.使用傳統的MyBatis提供的API,即statementId;

b. 使用Mapper介面

2.1.1 MyBatis使用Statement ID與資料庫互動

MyBatis 提供了非常方便和簡單的API,供用戶實作對資料庫的增刪改查資料操作,以及對資料庫連接資訊和MyBatis 自身配置資訊的維護操作,如下:

在這里插入圖片描述

如上圖所示,MyBatis使用API與資料庫互動,分為兩步驟:
(1)創建一個和資料庫打交道的SqlSession物件
(2)根據Statement Id 和引數傳遞給SqlSession物件,使用 SqlSession物件完成和資料庫的互動

SqlSession session = sqlSessionFactory.openSession();   // SqlSession物件
Blog blog =(Blog)session.selectOne("com.foo.bean.BlogMapper.selectByPrimaryKey",id); // statement Id

上述使用MyBatis 的方法,是創建一個和資料庫打交道的SqlSession物件,然后根據Statement Id 和引數來操作資料庫,這種方式固然很簡單和實用,但是它不符合面向物件語言的概念和面向介面編程的編程習慣,由于面向介面的編程是面向物件的大趨勢,MyBatis 為了適應這一趨勢,增加了第二種使用MyBatis 支持Mapper介面呼叫方式,

2.1.2 Mybatis使用Mapper介面與資料庫互動

Mybatis使用Mapper介面與資料庫互動,符合面向物件面向介面的設計原則,步驟如下:

(1)MyBatis 將組態檔中的每一個<mapper> 節點抽象為一個 Mapper 介面;

(2)Mapper介面中宣告的方法和跟<mapper> 節點中的<select|update|delete|insert> 節點項對應;

(3)<select|update|delete|insert> 節點的id值為Mapper 介面中的方法名稱,parameterType 值表示Mapper 對應方法的入參型別,而resultMap 值則對應了Mapper 介面表示的回傳值型別或者回傳結果集的元素型別,

在這里插入圖片描述

根據MyBatis 的配置規范配置好后,底層實際運行步驟是:

(1)實體化Mapper介面實作類:通過 SqlSession.getMapper(XXXMapper.class) 方法,MyBatis底層原始碼會根據相應的介面宣告的方法資訊,通過動態代理機制生成一個 Mapper 實體;

(2)方法名和引數串列作為方法簽名(方法的唯一標識),確定statement id,底層還是使用SqlSession呼叫sql陳述句:我們使用Mapper 介面的某一個方法時,MyBatis 會根據這個方法的方法名和引數型別,確定Statement Id,底層還是使用SqlSession.select("statementId",parameterObject);或者SqlSession.update("statementId",parameterObject); 等等來實作對資料庫的操作;

MyBatis 參考Mapper 介面這種呼叫方式兩個原因:

(1) 為了滿足面向介面編程的需要;
(2) 使得用戶在介面上可以使用注解來配置SQL陳述句,這樣就可以脫離XML組態檔,實作“0配置”,

小結:
Mybatis介面層提供兩個資料庫訪問方式,Mybatis Statement ID和Mapper介面,底層都是一樣的,使用mapper也是通過方法名和引數串列作為方法簽名,確定statement id,使用SqlSesson呼叫sql陳述句;
介面層在整個Mybatis四層第一層,其作用是:接收請求,開始呼叫sql陳述句,

2.2 資料處理層

資料處理層可以說是MyBatis 的核心,從大的方面上講,它要完成兩四個功能:

a. 通過傳入引數構建動態SQL陳述句;(引數映射+動態SQL陳述句生成)

b. SQL陳述句的執行以及封裝查詢結果集成List,(SQL執行+結果處理)

2.2.1 引數映射

引數映射指的是對于 java資料型別和jdbc資料型別之間的轉換:

這里有包括兩個程序:

第一,查詢階段,java型別的資料轉換成jdbc型別的資料,通過 preparedStatement.setXXX() 來設值;

第二,回傳階段,resultset查詢結果集中的 jdbcType 型別的資料轉換成java型別資料,

2.2.2 動態SQL陳述句生成

動態陳述句生成可以說是MyBatis框架非常優雅的一個設計,MyBatis 通過傳入的引數值,使用 Ognl 來動態地構造SQL陳述句,使得MyBatis 有很強的靈活性和擴展性,

2.2.3 SQL陳述句的執行

動態SQL陳述句生成之后,MyBatis 執行SQL陳述句,

2.2.4 結果處理

Mybatis執行完成SQL陳述句后,可能將回傳的結果集轉換成List 串列,

MyBatis 在對結果集的處理中,支持結果集關系一對多和多對一的轉換,并且有兩種支持方式,一種為 嵌套查詢陳述句的查詢,還有一種是 嵌套結果集的查詢,

面試官:談一談你對mybatis關聯查詢(Mapper介面的嵌套陳述句查詢和嵌套結果查詢)的理解?

2.3 框架支撐層

2.3.1 事務管理機制

事務管理機制對于ORM框架而言是不可缺少的一部分,事務管理機制的質量也是考量一個ORM框架是否優秀的一個標準,

2.3.2 連接池管理機制

由于創建一個資料庫連接所占用的資源比較大, 對于資料吞吐量大和訪問量非常大的應用而言,連接池的設計就顯得非常重要,

2.3.3 快取機制

為了提高資料利用率和減小服務器和資料庫的壓力,MyBatis 會對于一些查詢提供會話級別的資料快取,會將對某一次查詢,放置到SqlSession 中,在允許的時間間隔內,對于完全相同的查詢,MyBatis 會直接將快取結果回傳給用戶,而不用再到資料庫中查找,

2.3.4 SQL陳述句的配置方式

當介面層MyBatis使用Statement ID與資料庫時,框架支撐層只能使用XML配置方式;當介面層MyBatis使用Mapper介面與資料庫互動時,框架支撐層可以使用XML配置方式,也可以使用注解配置方式,推薦使用注解配置方式,符合面向物件的思想,后期引入通用mapper,單表查詢不再需要在mapper.xml中寫sql陳述句,

傳統的MyBatis 配置SQL 陳述句方式就是使用XML檔案進行配置的,但是這種方式不能很好地支持面向介面編程的理念,為了支持面向介面的編程,MyBatis 引入了Mapper介面的概念,面向介面的引入,對使用注解來配置SQL 陳述句成為可能,用戶只需要在介面上添加必要的注解即可(一般是就是@Mapper注解),不用再去配置XML檔案了,

Spring中,@Service @Controller注解 + @ComponentScan掃描注解,等效于 xml配置
Mybatis中,@Mapper注解 + @MapperScan掃描注解,等效于 xml配置
總之,注解+掃描 等效于 xml配置,

2.4 引導層

引導層是配置和啟動MyBatis 配置資訊的方式,

MyBatis 提供兩種方式來引導MyBatis :基于XML組態檔的方式和基于Java API 的方式,

三、MyBatis的主要構件及其相互關系

從MyBatis代碼實作的角度來看,MyBatis的主要的核心部件有以下幾個:

組件名稱作用
SqlSession(介面層)作為MyBatis作業的主要頂層API,表示和資料庫互動的會話,完成必要資料庫增刪改查功能
Executor(資料處理層-動態SQL陳述句生成)MyBatis執行器,是MyBatis 調度的核心,負責SQL陳述句的生成和查詢快取的維護
StatementHandler(資料處理層-動態SQL陳述句生成)封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設定引數、將Statement結果集轉換成List集合,
ParameterHandler(資料處理層-引數映射)負責對用戶傳遞的引數轉換成JDBC Statement 所需要的引數
ResultSetHandler(資料處理層-結果處理與映射)負責將JDBC回傳的ResultSet結果集物件轉換成List型別的集合
TypeHandler(資料處理層-引數映射)負責java資料型別和jdbc資料型別之間的映射和轉換
MappedStatement(介面層)MappedStatement維護了一條<select|update|delete|insert>節點的封裝
SqlSource(資料處理層-SQL決議)負責根據用戶傳遞的parameterObject,動態地生成SQL陳述句,將資訊封裝到BoundSql物件中,并回傳
BoundSql(資料處理層-動態SQL陳述句生成)表示動態生成的SQL陳述句以及相應的引數資訊
Configuration(全家配置)MyBatis所有的配置資訊都維持在Configuration物件之中,

注:這里只是列出了我個人認為屬于核心的部件,請讀者不要先入為主,認為MyBatis就只有這些部件哦!每個人對MyBatis的理解不同,分析出的結果自然會有所不同,歡迎讀者提出質疑和不同的意見,我們共同探討,它們的關系如下圖所示:

在這里插入圖片描述

mybatis和jdbc是兩個不同的東西

(1) mybatis是服務端程式的一個ORM框架,物件關系映射框架,上圖中只解釋mybatis十個組件,不解釋jdbc的組件,

(2) jdbc是java database connection,java資料庫連接工具,只用于java陳述句,但是可以連接不同資料庫,一般是mysql,

mybatis在服務端程式的下面,jdbc在資料庫的上面,所以上圖中,mybatis在上面,jdbc在下面,

四、從MyBatis一次select 查詢陳述句來分析MyBatis的架構設計

4.1 從宏觀的角度看

4.1.1 資料庫資料準備

   --創建一個員工基本資訊表
    create  table "EMPLOYEES"(
        "EMPLOYEE_ID" NUMBER(6) not null,
       "FIRST_NAME" VARCHAR2(20),
       "LAST_NAME" VARCHAR2(25) not null,
       "EMAIL" VARCHAR2(25) not null unique,
       "SALARY" NUMBER(8,2),
        constraint "EMP_EMP_ID_PK" primary key ("EMPLOYEE_ID")
    );
    comment on table EMPLOYEES is '員工資訊表';
    comment on column EMPLOYEES.EMPLOYEE_ID is '員工id';
    comment on column EMPLOYEES.FIRST_NAME is 'first name';
    comment on column EMPLOYEES.LAST_NAME is 'last name';
    comment on column EMPLOYEES.EMAIL is 'email address';
    comment on column EMPLOYEES.SALARY is 'salary';
    
    --添加資料
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (100, 'Steven', 'King', 'SKING', 24000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (101, 'Neena', 'Kochhar', 'NKOCHHAR', 17000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (102, 'Lex', 'De Haan', 'LDEHAAN', 17000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (103, 'Alexander', 'Hunold', 'AHUNOLD', 9000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (104, 'Bruce', 'Ernst', 'BERNST', 6000.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (105, 'David', 'Austin', 'DAUSTIN', 4800.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (106, 'Valli', 'Pataballa', 'VPATABAL', 4800.00);
	
	insert into EMPLOYEES (EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY)
	values (107, 'Diana', 'Lorentz', 'DLORENTZ', 4200.00);    

4.1.2 Mybatis組態檔

組態檔一般命名為mybatisConfig.xml(名稱任意),內容如下:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
	 <property name="driver" value="oracle.jdbc.driver.OracleDriver" />  
         <property name="url" value="jdbc:oracle:thin:@localhost:1521:xe" />  
         <property name="username" value="louis" />  
         <property name="password" value="123456" />
      </dataSource>
    </environment>
  </environments>
    <mappers>
       <mapper  resource="com/louis/mybatis/domain/EmployeesMapper.xml"/>
    </mappers>
</configuration>

mapper的識別兩種方式:
mybatisConfig.xml中配置 或者 @Mapper注解+@MapperScan注解

4.1.3 引數映射

POJO+XxxMapper.java+XxxMapper.xml

package com.louis.mybatis.model;
 
import java.math.BigDecimal;
 
public class Employee {
    private Integer employeeId;
 
    private String firstName;
 
    private String lastName;
 
    private String email;
 
    private BigDecimal salary;
 
    public Integer getEmployeeId() {
        return employeeId;
    }
 
    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public BigDecimal getSalary() {
        return salary;
    }
 
    public void setSalary(BigDecimal salary) {
        this.salary = salary;
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.louis.mybatis.dao.EmployeesMapper" >
 
  <resultMap id="BaseResultMap" type="com.louis.mybatis.model.Employee" >
    <id column="EMPLOYEE_ID" property="employeeId" jdbcType="DECIMAL" />
    <result column="FIRST_NAME" property="firstName" jdbcType="VARCHAR" />
    <result column="LAST_NAME" property="lastName" jdbcType="VARCHAR" />
    <result column="EMAIL" property="email" jdbcType="VARCHAR" />
    <result column="SALARY" property="salary" jdbcType="DECIMAL" />
  </resultMap>
  
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    	EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
    	from LOUIS.EMPLOYEES
    	where EMPLOYEE_ID = #{employeeId,jdbcType=DECIMAL}
  </select>
</mapper>

XxxMapper.xml中用來寫sql陳述句,使用通用mapper之后,單表查詢不用在寫XxxMapper.xml中寫sql陳述句了,

4.1.4 相關依賴

pom.xml中,匯入mybatis和jdbc依賴,如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
 
  <groupId>batis</groupId>
  <artifactId>batis</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>jar</packaging>
 
  <name>batis</name>
  <url>http://maven.apache.org</url>
 
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
 
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
 
    <dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.2.7</version>
	</dependency>
    
	<dependency>
		<groupId>com.oracle</groupId>
		<artifactId>ojdbc14</artifactId>
		<version>10.2.0.4.0</version>
	</dependency>
    
  </dependencies>
</project>

4.1.5 客戶端代碼

package com.louis.mybatis.test;
 
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 
import com.louis.mybatis.model.Employee;
 
/**
 * SqlSession 簡單查詢演示類
 * @author louluan
 */
public class SelectDemo {
 
	public static void main(String[] args) throws Exception {
		/*
		 * 1.加載mybatis的組態檔,初始化mybatis,創建出SqlSessionFactory,是創建SqlSession的工廠
		 * 這里只是為了演示的需要,SqlSessionFactory臨時創建出來,在實際的使用中,SqlSessionFactory只需要創建一次,當作單例來使用
		 */
		InputStream inputStream = Resources.getResourceAsStream("mybatisConfig.xml");
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		SqlSessionFactory factory = builder.build(inputStream);
		
		//2. 從SqlSession工廠 SqlSessionFactory中創建一個SqlSession,進行資料庫操作
		SqlSession sqlSession = factory.openSession();
	
		//3.使用SqlSession查詢
		Map<String,Object> params = new HashMap<String,Object>();
		
		params.put("min_salary",10000);
		//a.查詢工資低于10000的員工
		List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);
		//b.未傳最低工資,查所有員工
		List<Employee> result1 = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary");
		System.out.println("薪資低于10000的員工數:"+result.size());
		//~output :   查詢到的資料總數:5  
		System.out.println("所有員工數: "+result1.size());
		//~output :  所有員工數: 8
	}
 
}

從4.1.1到4.1.5,表設計、mybatisConfig.xml(mapper的兩種方式,xml中配置或者@Mapper+@MapperScan)、POJO+XxxMapper.java+XxxMapper.xml(使用通用mapper,單表操作不用寫sql陳述句)、依賴匯入(mybatis依賴和jdbc依賴)、客戶端代碼(從組態檔得到InputStream,然后SqlSessionFactoryBuilder、然后SqlSessionFactory、然后SqlSession、傳入statementid和引數,執行sql陳述句,拿到結果集)

4.2 從原始碼的角度看mybatis查詢

這里介紹SqlSession的整個作業程序

4.2.1 開啟一個資料庫訪問會話

我們所說的開啟一個資料庫訪問會話,代碼實作就是創建SqlSession物件,如下:

SqlSession sqlSession = factory.openSession();

MyBatis封裝了對資料庫的訪問,把對資料庫的會話和事務控制放到了SqlSession物件中,

在這里插入圖片描述

如上圖所示,SqlSession中兩個內容:事務控制+資料查詢,事務控制就是commit和rollback,資料查詢就是 select update insert delete,

4.2.2 SqlSession組件

SqlSession物件呼叫selectList方法,傳遞一個配置Sql陳述句的StatementId和引數,然后回傳結果,代碼如下:

List<Employee> result = sqlSession.selectList("com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",params);

上述的"com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary",是配置在EmployeesMapper.xml 的Statement ID,params 是傳遞的查詢引數,

注意:這里是statementId就是 類名+方法名

讓我們來看一下sqlSession.selectList()方法的定義:

  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }
 
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      //1.根據Statement Id,在mybatis配置物件Configuration中查找和組態檔相對應的MappedStatement	
      MappedStatement ms = configuration.getMappedStatement(statement);
      //2. 將查詢任務委托給MyBatis 的執行器 Executor
      List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
      return result;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

MyBatis在初始化的時候,會將MyBatis的配置資訊全部加載到記憶體中,使用org.apache.ibatis.session.Configuration實體來維護,使用者可以使用sqlSession.getConfiguration()方法來獲取,MyBatis的組態檔中配置資訊的組織格式和記憶體中物件的組織格式幾乎完全對應的,上述例子中的

  <select id="selectByMinSalary" resultMap="BaseResultMap" parameterType="java.util.Map" >
    select 
    	EMPLOYEE_ID, FIRST_NAME, LAST_NAME, EMAIL, SALARY
    	from LOUIS.EMPLOYEES
    	<if test="min_salary != null">
    		where SALARY < #{min_salary,jdbcType=DECIMAL}
    	</if>
  </select>

加載到記憶體中會生成一個對應的MappedStatement物件,然后會以key=“com.louis.mybatis.dao.EmployeesMapper.selectByMinSalary” ,value為MappedStatement物件的形式維護到Configuration的一個Map中,當以后需要使用的時候,只需要通過Id值來獲取就可以了,

從上述的代碼中我們可以看到SqlSession的職能是:SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然后呼叫mybatis執行器Executor來執行具體的操作,代碼如下:

 //1.根據Statement Id,在mybatis 配置物件Configuration中查找和組態檔相對應的MappedStatement,Configuration里面是key,value,這里根據key得到value
MappedStatement ms = configuration.getMappedStatement(statement);
//2. 將查詢任務委托給MyBatis 的執行器 Executor執行
List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

SqlSession的使命完成,看Executor的表演,

4.2.3 Executor執行器

MyBatis執行器Executor根據SqlSession傳遞的引數執行query()方法,如下:

/**
* BaseExecutor 類部分代碼
*
*/
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	  
	// 1.根據具體傳入的引數,動態地生成需要執行的SQL陳述句,用BoundSql物件表示  
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2.為當前的查詢創建一個快取Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }
 
  @SuppressWarnings("unchecked")
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	// 3.快取中沒有值,直接從資料庫中讀取資料  
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }
 private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      //4. 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

進一步深入,doQuery()方法是怎么執行的?

/**
*
*SimpleExecutor類的doQuery()方法實作
*
*/
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //5. 根據既有的引數,創建StatementHandler物件來執行查詢操作
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //6. 創建java.Sql.Statement物件,傳遞給StatementHandler物件
      stmt = prepareStatement(handler, ms.getStatementLog());
      //7. 呼叫StatementHandler.query()方法,回傳List結果集
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

上述的Executor.query()方法幾經轉折,最后會創建一個StatementHandler物件,然后將必要的引數傳遞給StatementHandler,使用StatementHandler來完成對資料庫的查詢,最侄訓傳List結果集,

從上面的代碼中我們可以看出,Executor的功能和作用是:

(1、根據傳遞的引數,完成SQL陳述句的動態決議,生成BoundSql物件,供StatementHandler使用

// 1.根據具體傳入的引數,動態地生成需要執行的SQL陳述句,用BoundSql物件表示  
BoundSql boundSql = ms.getBoundSql(parameter); 

(2、為查詢創建快取,以提高性能

// 2.為當前的查詢創建一個快取Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 這個query()中同時用到了動態生成的boundSql和快取key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 

(3、快取中沒有值,直接從資料庫中讀取資料

  if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  } else {
	// 3.快取中沒有值,直接從資料庫中讀取資料  
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

(4、 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中

try {
  //4. 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
  localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
}
return list;

(5、具體doQuery()查詢中,創建JDBC的Statement連接物件,傳遞給StatementHandler物件,回傳List查詢結果

  //5. 根據既有的引數,創建StatementHandler物件來執行查詢操作
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  //6. 創建java.Sql.Statement物件,傳遞給StatementHandler物件
  stmt = prepareStatement(handler, ms.getStatementLog());
  //7. 呼叫StatementHandler.query()方法,回傳List結果集
  // StatementHandler作為呼叫者,Statement作為實參
  return handler.<E>query(stmt, resultHandler);

好了,Executor的任務完成了,看StatementHandler和Statement的表演,核心就是:

  // StatementHandler作為呼叫者,Statement作為實參
  return handler.<E>query(stmt, resultHandler);

4.2.4 StatementHandler與Statement

StatementHandler物件負責設定Statement物件中的查詢引數、處理JDBC回傳的resultSet,將resultSet加工為List集合并回傳,doQuery() 方法的實作,如下(接著上面的Executor第六步和第七步):

/**
*
*SimpleExecutor類的doQuery()方法實作
*
*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { 
Statement stmt = null; 
try { 
   Configuration configuration = ms.getConfiguration(); 
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); 
   // 1.準備Statement物件,并設定Statement物件的引數 
   stmt = prepareStatement(handler, ms.getStatementLog()); 
   // 2. StatementHandler執行query()方法,回傳List結果 
   return handler.<E>query(stmt, resultHandler); 
   } finally { 
   closeStatement(stmt); 
   } 
 }
 
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    //對創建的Statement物件設定引數,即設定SQL 陳述句中 ? 設定為指定的引數
    handler.parameterize(stmt);
    return stmt;
  }

以上我們可以總結StatementHandler物件主要完成兩個作業:

(1. 設定引數:對于JDBC的PreparedStatement型別的物件,創建的程序中,我們使用的是SQL陳述句字串會包含 若干個? 占位符,我們其后再對占位符進行設值,StatementHandler通過parameterize(statement)方法對Statement進行設值;

(2. 查詢:StatementHandler通過List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement物件回傳的resultSet封裝成List,

StatementHandler 的兩個作用:設定引數、查詢,

4.2.4.1 設定引數

設定引數:StatementHandler 的parameterize(statement) 方法的實作

/**
*   StatementHandler 類的parameterize(statement) 方法實作 
*/
public void parameterize(Statement statement) throws SQLException {
	//使用ParameterHandler物件來完成對Statement的設值  
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  /**
   * 
   *ParameterHandler類的setParameters(PreparedStatement ps) 實作
   * 對某一個Statement進行設定引數
   */
  public void setParameters(PreparedStatement ps) throws SQLException {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          
          // 每一個Mapping都有一個TypeHandler,根據TypeHandler來對preparedStatement進行設定引數
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) jdbcType = configuration.getJdbcTypeForNull();
          // 設定引數,這里將java型別變為jdbc型別
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        }
      }
    }
  }

從上述的代碼可以看到,StatementHandler 的parameterize(Statement) 方法呼叫了 ParameterHandler的setParameters(statement) 方法,這個方法根據我們輸入的引數,對statement物件的 ? 占位符處進行賦值,如下:

typeHandler.setParameter(ps, i + 1, value, jdbcType);

解釋一下setParameter方法:根據boundSql動態Sql得到一個parameterMappings,然后遍歷這個list,對于每一個parameterMapping,從parameterMapping得到propertyName,從propertyName得到value,然后,從parameterMapping得到typeHandler,從parameterMapping得到jdbcType ,最后使用 typeHandler.setParameter(ps, i + 1, value, jdbcType);

4.2.4.2 查詢

查詢:StatementHandler 的List<E> query(Statement statement, ResultHandler resultHandler)方法的實作

  /**
   * PreParedStatement類的query方法實作
   */
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
	// 1.呼叫preparedStatemnt.execute()方法,然后將resultSet交給ResultSetHandler處理  
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    //2. 使用ResultHandler來處理ResultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }

StatementHandler將查詢的業務交給ResultSetHandler來完成,下面的主角成為ResultSetHandler了

/**  
* DefaultResultSetHandler類的handleResultSets()方法實作
*
*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
 
    int resultSetCount = 0;
    ResultSetWrapper rsw = getFirstResultSet(stmt);
 
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      
      //將resultSet
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
 
    String[] resultSets = mappedStatement.getResulSets();
    if (resultSets != null) {
      while (rsw != null && resultSetCount < resultSets.length) {
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }
 
    return collapseSingleResultList(multipleResults);
  }

在這里插入圖片描述
在這里插入圖片描述

從上述代碼我們可以看出,StatementHandler 的List query(Statement statement, ResultHandler resultHandler)方法的實作,是呼叫了ResultSetHandler的handleResultSets(Statement) 方法,ResultSetHandler的handleResultSets(Statement) 方法會將Statement陳述句執行后生成的resultSet 結果集轉換成List 結果集,

4.2.5 小結

第一個主角SqlSession

SqlSession根據Statement ID, 在mybatis配置物件Configuration中獲取到對應的MappedStatement物件,然后呼叫mybatis執行器來執行具體的操作,

  //1.根據Statement Id,在mybatis 配置物件Configuration中查找和組態檔相對應的MappedStatement,Configuration里面是key,value,這里根據key得到value
  MappedStatement ms = configuration.getMappedStatement(statement);
  //2. 將查詢任務委托給MyBatis 的執行器 Executor執行
  List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

SqlSession的使命完成,看Executor的表演,

第二個主角Executor

Executor的功能和作用是:

(1、根據傳遞的引數,完成SQL陳述句的動態決議,生成BoundSql物件,供StatementHandler使用

// 1.根據具體傳入的引數,動態地生成需要執行的SQL陳述句,用BoundSql物件表示  
BoundSql boundSql = ms.getBoundSql(parameter); 

(2、為查詢創建快取,以提高性能

// 2.為當前的查詢創建一個快取Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 這個query()中同時用到了動態生成的boundSql和快取key
return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 

(3、快取中沒有值,直接從資料庫中讀取資料

  if (list != null) {
    handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
  } else {
	// 3.快取中沒有值,直接從資料庫中讀取資料  
    list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

(4、 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中

try {
  //4. 執行查詢,回傳List 結果,然后將查詢的結果放入快取之中
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
  localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
}
return list;

(5、具體doQuery()查詢中,創建JDBC的Statement連接物件,傳遞給StatementHandler物件,回傳List查詢結果

  //5. 根據既有的引數,創建StatementHandler物件來執行查詢操作
  StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  //6. 創建java.Sql.Statement物件,傳遞給StatementHandler物件
  stmt = prepareStatement(handler, ms.getStatementLog());
  //7. 呼叫StatementHandler.query()方法,回傳List結果集
  return handler.<E>query(stmt, resultHandler);

第三個主角StatementHandler、第四個主角ParameterHandlder、第五個主角TypeHandler和第六個主角ResultSetHandler

以上我們可以總結StatementHandler物件主要完成兩個作業:

(1. 設定引數:對于JDBC的PreparedStatement型別的物件,創建的程序中,我們使用的是SQL陳述句字串會包含 若干個? 占位符,我們其后再對占位符進行設值,StatementHandler通過parameterize(statement)方法對Statement進行設值;

typeHandler.setParameter(ps, i + 1, value, jdbcType);

解釋一下setParameter方法:根據boundSql動態Sql得到一個parameterMappings,然后遍歷這個list,對于每一個parameterMapping,從parameterMapping得到propertyName,從propertyName 得到value,然后,從parameterMapping得到typeHandler,從parameterMapping得到jdbcType ,最后使用 typeHandler.setParameter(ps, i + 1, value, jdbcType);

(2.查詢:StatementHandler通過List query(Statement statement, ResultHandler resultHandler)方法來完成執行Statement,和將Statement物件回傳的resultSet封裝成List,

StatementHandler將查詢的業務交給ResultSetHandler來完成,對于ResultSetHandler,回傳的時候應該也是要用到TypeHandler類將jdbc型別轉換為java型別,但是沒找到,DefaultResultSetHandler類中搜索getTypeHandler()就好了,

五、尾聲

《沿著哈勃望遠,看清MyBatis整體架構》,完成了,

天天打碼,天天進步!!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/306422.html

標籤:java

上一篇:拿到貝殼和廣聯達的轉正意向書了!坐等開獎。。。

下一篇:1.序言—為什么建立自己的博客?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more