MyBatis學習筆記(一)

MyBatis(一)

MyBatis學習筆記(一), 內容包括:html

  1. MyBatis入門案例
  2. MyBatis核心配置文件結構說明
  3. sql映射文件結構說明
  4. 查詢結果封裝resultMap
  5. 多對一查詢association、一對多查詢collection

學習視頻:尚硅谷雷豐陽老師MyBatis
https://www.bilibili.com/video/BV1bb411A7bDjava

1. MyBatis簡介

鏈接數據庫的方法:mysql

工具:JDBC→Dbutils(QueryRunner)→JdbcTemplate
image-20200607234736779.pnggit

框架:總體解決方案github

Hibernate:全自動全映射ORM(Object Relation Mapping)框架,旨在消除SQLsql

image-20200607234852037.png

缺點:數據庫

  1. 長難複雜SQL,對於Hibernate而言處理也不容易
  2. 內部自動生產的SQL,不容易作特殊優化。對開發人員而言,核心sql仍是須要本身優化( HQL)
  3. 基於全映射的全自動框架,大量字段的POJO進行部分映射時比較困難,致使數據庫性能降低

SQL和Java編碼分開,功能邊界清晰,一個專一業務、一個專一數據。數組

MyBatis:半自動,輕量級的框架,sql語句提取到配置文件中編寫安全

image-20200608225144698.png

  • MyBatis 是一個半自動化的持久化層框架,支持自定義 SQL、存儲過程以及高級映射。
  • MyBatis 免除了幾乎全部的 JDBC 代碼以及設置參數和獲取結果集的工做。
  • MyBatis 能夠經過簡單的 XML 或註解來配置和映射原始類型、接口和 Java POJO映射成數據庫中的記錄。
  • 項目地址:https://github.com/mybatis/my...
  • Maven 依賴:網絡

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>x.x.x</version>
    </dependency>

官方文檔:https://mybatis.org/mybatis-3...

2. MyBatis案例之HelloWorld

核心步驟:

  1. 數據庫表與對應的實體類Employee接口EmployeeMapper
  2. 全局配置文件(核心配置文件)mybatis-config.xml
  3. 根據全局配置文件建立SqlSessionFactory對象,SqlSessionFactoryBulid→SqlSessionFactory→SqlSession
  4. sql映射文件EmployeeMapper.xml,配置了每個sql,以及sql的封裝規則,須要在全局配置文件中註冊sql映射文件
  5. 由sqlSession對象實例執行sql語句,或者獲取接口的代理對象執行

2.1 數據庫環境

建立tbl_employee表並插入三條數據:

CREATE TABLE tbl_employee(
id INT(10) PRIMARY KEY AUTO_INCREMENT,  
last_name VARCHAR(255),
gender CHAR(1),
email VARCHAR(255)
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO tbl_employee VALUES
(1,"zhansgan",0,"zhangsan@qq.com"),
(2,"lisi",0,"lisi@163.com"),
(3,"wangwu",1,"wangwu@126.com");

2.2 建立實體類

  1. 新建一個普通的Maven項目;
  2. 在pom.xml文件中導入Maven依賴,包括:mysql-connector-java,mybatisjuint,log4j
  3. 建立與數據庫表對應的實體類Employee,名稱與數據庫表的字段保持一致。若是不一致查詢會出問題,能夠用別名解決
  • 項目依賴:
<dependencies>
           <!--mybatis-->
           <dependency>
               <groupId>org.mybatis</groupId>
               <artifactId>mybatis</artifactId>
               <version>3.5.4</version>
           </dependency>
           <!--mysql-->
           <dependency>
               <groupId>mysql</groupId>
               <artifactId>mysql-connector-java</artifactId>
               <version>5.1.47</version>
           </dependency>
           <!--junit-->
           <dependency>
               <groupId>junit</groupId>
               <artifactId>junit</artifactId>
               <version>4.13</version>
           </dependency>
           <!--log4j -->
           <dependency>
               <groupId>log4j</groupId>
               <artifactId>log4j</artifactId>
               <version>1.2.17</version>
           </dependency>
       </dependencies>
  • 實體類com.xiao.pojo.Employee:
public class Employee {
    private Integer id;
    private String lastName; //注意,該名稱與數據庫表的字段名不一致,查詢會出現問題,能夠用別名解決
    private String email;
    private String gender;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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 String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}

2.3 MyBatis核心配置文件

  • 在resources目錄下編寫mybatis核心配置文件: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>
    <!--配置環境組,選擇默認的環境id-->
    <environments default="development">
        <!--配置單個環境並指定id爲development-->
        <!--能夠同時配置多個環境,可是隻能選擇一個使用-->
        <environment id="development">
            <!--配置事務管理類型,JDBC-->
            <transactionManager type="JDBC"/>
            <!--配置鏈接池POOLED-->
            <dataSource type="POOLED">
                <!--數據庫鏈接池4個參數-->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
        <!--配置映射,註冊Mapper文件-->
    <mappers>
        <mapper resource="com/xiao/dao/EmployeeMapper.xml"/>
    </mappers>
</configuration>

注意:

在xml配置文件中,url中的 &符號須要寫成 &amp;

2.4 sql映射文件

在resources目錄下表編寫sql映射文件:EmployeeMapper.xml
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">

<!--namespace:名稱空間,能夠綁定一個Mapper接口-->
<!--編寫sql語句-->
<!--id:惟一標識,若是有Mapper接口文件,須要對應其中的方法名-->
<!--resultType:對應返回結果類型-->
<!--#{id}:從傳遞過來的參數中取出id值-->
<mapper namespace="com.xiao.dao.EmployeeMapper">
    <select id="selectEmp" resultType="com.xiao.pojo.Employee">
        select  * from tbl_employee where id = #{id}
    </select>
</mapper>

2.5 測試

根據全局配置文件建立SqlSessionFactory對象,SqlSessionFactoryBulid→SqlSessionFactory→SqlSession。由sqlSession對象實例執行sql語句。

public class MyBatisTest {

    @Test
    public void test() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //由SqlSessionFactoryBuilder對象獲取SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //由SqlSession工廠得到SqlSession對象,使用其進行增刪改查
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
            //sqlSession,直接執行已經映射的sql語句
            //selectOne()中兩個參數:sql的惟一標識(對應sql映射文件中的namespace.id)和執行sql須要的參數
            Employee employee = sqlSession.selectOne("com.xiao.dao.EmployeeMapper.selectEmp", 1);
            System.out.println(employee);
        } finally {
            //一個sqlSession就是和數據庫的一次會話,使用完以後須要關閉資源
            sqlSession.close();
        }
    }
}

查詢結果:

Employee{id=1, lastName='null', email='zhangsan@qq.com', gender='0'}

因爲實體類的成員變量名和字段名不一致,所以lastName查詢結果爲null,能夠在sql語句中取別名解決,也能夠在sql映射文件中配置,後面會講到。

修改sql語句:

select  id,last_name lastName,gender,email from tbl_employee where id = #{id}

查詢結果:

Employee{id=1, lastName='zhansgan', email='zhangsan@qq.com', gender='0'}

2.6 接口文件

一種更有效的方法:建立接口文件com.xiao.dao.EmployeeMapper與sql映射文件綁定,在接口文件中定義方法,能夠聲明操做的返回值和參數。
注意:

  1. sql映射文件中的namesapce要與對應的接口文件全路徑名一致
  2. sql映射文件中的sql語句標籤中的id要與對應的接口文件中的方法名一致
public interface EmployeeMapper {
    //定義一個查詢方法
    Employee selectEmployeeById(Integer id);
}

測試文件:

try {        //得到接口文件的代理對象,執行方法
            EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
            System.out.println(mapper.selectEmployeeById(1));
        } finally {
            sqlSession.close();
        }

3. 幾個類的說明

3.1 SqlSessionFactoryBuilder

  • 構造器SqlSessionFactoryBuilder ,能夠從 XML 配置文件或一個預先配置的 Configuration 實例來構建出 SqlSessionFactory 實例。
  • 方法: build(InputStream in)

3.2 SqlSessionFactory

  • SqlSession工廠,每一個基於 MyBatis 的應用都以一個 SqlSessionFactory 的實例爲核心,由SqlSessionFactoryBuilder類建立。
  • SqlSessionFactory類能夠用於建立SqlSession類,方法:openSession(),傳入參數true能夠設置爲自動提交事務。

3.3 SqlSession

  • SqlSession表明和數據庫的一次會話,用完必須關閉,是非線程安全的。能夠從SqlSessionFactory中得到 SqlSession 的實例;
  • SqlSession 提供了在數據庫執行 SQL 命令所需的全部方法,能夠經過 SqlSession 實例來得到映射器接口的代理對象,即接口和xml文件進行綁定。方法:getMapper(),須要傳入dao接口的class類型參數UserDao.class

3.4 做用域(Scope)和生命週期

  • SqlSessionFactoryBuilder:

    能夠理解爲數據庫鏈接池對象,一旦建立了 SqlSessionFactory,就再也不須要它了,所以最佳做用域是方法做用域(也就是局部方法變量);

  • SqlSessionFactory:

    能夠理解爲數據庫鏈接對象,一旦被建立就應該在應用的運行期間一直存在,所以 SqlSessionFactory 的最佳做用域是應用做用域。使用單例模式或者靜態單例模式

  • SqlSession:

    鏈接到鏈接池的一個請求,每一個線程都有它本身的 SqlSession 實例。SqlSession 的實例不是線程安全的,所以是不能被共享的,因此它的最佳的做用域是請求或方法做用域。

    爲了確保每次都能執行關閉操做,把關閉操做放到 finally 塊中。

4. 全局配置文件

約束文件:

<?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">

MyBatis 的配置文件包含了會深深影響 MyBatis 行爲的設置和屬性信息。 配置文檔的頂層結構以下,必須按順序配置

  • configuration(配置)

    • properties(屬性)
    • settings(設置)
    • typeAliases(類型別名)
    • typeHandlers(類型處理器)
    • objectFactory(對象工廠)
    • plugins(插件)
    • environments(環境配置)

      • environment(環境變量)

        • transactionManager(事務管理器)
        • dataSource(數據源)
    • databaseIdProvider(數據庫廠商標識)
    • mappers(映射器)

4.1 屬性(properties)

經過properties屬性能夠實現引用配置文件,這些屬性都是能夠外部配置且可動態替換的。

  • 能夠在覈心配置文件中配置數據庫鏈接池的4個參數,設置好的參數能夠在整個配置文件中替換須要動態配置的屬性值:
<properties>
     <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="root"/>
 </properties>
....
<!--使用${name}引用相應的值-->
<dataSource type="POOLED">
  <property name="driver" value="${driver}"/>
  <property name="url" value="${url}"/>
  <property name="username" value="${username}"/>
  <property name="password" value="${password}"/>
</dataSource>
....
  • 也能夠引入外部的配置文件,例如編寫一個外部的配置文件db.properties,而後在覈心配置文件中引入:

    • resource:引用類路徑下的資源
    • url:引用網絡路徑或者磁盤路徑下的資源
#外部配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=root
password=root

核心配置文件:

<!--在覈心配置文件中引入外部配置文件,而後就可使用${name}引用-->
    <properties resource="db.properties">
    </properties>

【注意事項】:若是一個屬性在不僅一個地方進行了配置,那麼,MyBatis 將按照下面的順序來加載:

  • 首先讀取在 properties 元素體內指定的屬性。
  • 而後根據 properties 元素中的 resource 屬性讀取類路徑下屬性文件,或根據 url 屬性指定的路徑讀取屬性文件,並覆蓋以前讀取過的同名屬性。
  • 最後讀取做爲方法參數傳遞的屬性,並覆蓋以前讀取過的同名屬性。

所以,經過方法參數傳遞的屬性具備最高優先級,resource/url 屬性中指定的配置文件次之,最低優先級的則是 properties 元素中指定的屬性。

5.2 設置(settings)

是 MyBatis 中極爲重要的調整設置,它們會改變 MyBatis 的運行時行爲。

經常使用設置:

  • mapUnderscoreToCamelCase:是否開啓駝峯命名自動映射,即從經典數據庫列名 A_COLUMN 映射到經典 Java 屬性名 aColumn,默認爲flase
<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
  • jdbcTypeForNull:當沒有爲參數指定特定的 JDBC 類型時,空值的默認 JDBC 類型。默認是 OTHER,可是若是用Oracle數據庫,須要改成NULL
  • autoMappingBehavior:指定 MyBatis 應如何自動映射列到字段或屬性。 NONE 表示關閉自動映射;PARTIAL 只會自動映射沒有定義嵌套結果映射的字段。 FULL 會自動映射任何複雜的結果集(不管是否嵌套),默認爲PARTIAL
  • lazyLoadingEnabled:延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。 特定關聯關係中可經過設置 fetchType 屬性來覆蓋該項的開關狀態。默認是false
  • aggressiveLazyLoading:開啓時,任一方法的調用都會加載該對象的全部延遲加載屬性。 不然,每一個延遲加載屬性會按需加載。

5.3 類型別名(typeAliases)

類型別名可爲 Java 類型設置一個縮寫名字。 僅用於 XML 配置,意在下降冗餘的全限定類名書寫。別名不區分大小寫

  • 單個類起別名,<typeAlias>標籤:

    • type:指定要起別名的類型全類名,默認是類名小寫employee(其實不區分大小寫)
    • alias:指定新的別名
<typeAliases>
<!-- type:指定要起別名的類型全類名,默認是類名小寫employee-->
<!-- alias:指定新的別名-->
   <typeAlias type="com.xiao.pojo.Employee"/>
 </typeAliases>
  • 批量起別名,用<package>標籤,
<typeAliases>
      <package name="com.xiao.pojo"/>
 </typeAliases>
  • 批量狀況下,若是指定包下的子包中有同名的類,會產生衝突,可使用註解解決:在實體類上註解@Alias,則別名爲其註解值:
@Alias("Emp")
public class Employee {
    ...
}

常見的 Java 類型內建的類型別名。它們都是不區分大小寫的,自定義的別名不要與其重複。
基本類型前面加下劃線,引用類型首字母小寫:

別名 映射的類型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

5.4 類型處理器(typeHandlers)

數據庫裏的字段類型與Java的數據類型進行映射。
在設置預處理語句(PreparedStatement)中的參數或從結果集中取出一個值時, 都會用類型處理器將獲取到的值以合適的方式轉換成 Java 類型。

5.5 插件(plugins)

MyBatis 容許在映射語句執行過程當中的某一點進行攔截調用。默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • 參數處理器:ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • sql語句處理器:StatementHandler (prepare, parameterize, batch, update, query)

5.6 環境配置(environments)

MyBatis 能夠配置成適應多種環境,能夠配置多個環境,但每一個 SqlSessionFactory 實例只能選擇一種環境。default參數指定使用某種環境。

  • transactionManager:事務管理器,有兩種類型,type="[JDBC|MANAGED]",默認的事務管理器就是JDBC。也能夠自定義事務管理器:實現TransactionFactory接口,type指定爲全類名
  • dataSource:數據源,有三種內建的數據源類型, type="[UNPOOLED|POOLED|JNDI]"),也能夠本身實現DataSourceFactory接口自定義數據源
<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>

【注意事項】:

  • 默認使用的環境 id(好比:default="development")。
  • 每一個 environment 元素定義的環境 id(好比:id="development")。
  • 事務管理器的配置(好比:type="JDBC")。
  • 數據源的配置(好比:type="POOLED")。

5.7 數據庫廠商標識(databaseIdProvider)

MyBatis 能夠根據不一樣的數據庫廠商執行不一樣的語句,考慮了移植性。
獲得數據庫廠商的標識(驅動getDatabaseProductName()),MyBatis就能根據數據庫廠商標識來執行不一樣的sql。

  1. 爲不一樣的數據庫的廠商標識取別名:
  2. 查詢語句標籤中指定databaseId元素值,會優先執行帶該標識的,不帶標籤的語句會捨棄
<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>

5.8 映射器(mappers)

須要告訴 MyBatis 到哪裏去找到 sql語句。可使用相對於類路徑的資源引用,或徹底限定資源定位符(包括 file:/// 形式的 URL),或類名和包名等。

  • resource:指定sql映射文件的路徑
  • url:使用網上的資源或者使用本地磁盤路徑上的文件
  • class:註冊接口,若是有sql映射文件,映射文件必須和接口同目錄同名;也能夠在接口上寫註解,這樣就不須要sql映射文件,
  • package:批量註冊,將包內的映射器接口實現所有註冊爲映射器,做用同class
<mappers>
    <!-- 使用相對於類路徑的資源引用 -->
    <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    <!-- 使用徹底限定資源定位符(URL) -->
    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    <!-- 使用映射器接口實現類的徹底限定類名 -->
    <mapper class="org.mybatis.builder.AuthorMapper"/>
    <!-- 將包內的映射器接口實現所有註冊爲映射器 -->
     <package name="org.mybatis.builder"/>
</mappers>

在dao接口文件中,用註解實現查詢:

public interface EmployeeMapper {
    //定義一個查詢方法
    @Select("select * from tbl_employee where id = #{id}")
    Employee selectEmployeeById(Integer id);
}

【一個小坑】:

  1. class:註冊接口,若是有sql映射文件,映射文件必須和接口同目錄同名
  2. 在resources中創建多級目錄時,用 / 隔開,可是建立完後顯示的是 . 。若是新建目錄時用 .會出錯。

image-20200609005555811.png

5. 映射文件

映射文件指導MyBatis如何進行數據庫增刪改。

5.1 基本的增刪改查

  • insert –- 映射插入語句
  • update –- 映射更新語句
  • delete –- 映射刪除語句
  • select –- 映射查詢語句

1) 首先在接口文件com.xiao.dao.EmployeeMapper中聲明對應的增刪改查方法,
同時能夠設置返回值類型Integer、Long、Boolean,表示被影響的行數

public interface EmployeeMapper {

    //定義一個查詢方法
    @Select("select * from tbl_employee where id = #{id}")
    Employee selectEmployeeById(Integer id);

    //增長
    void addEmp(Employee employee);
    //根據id刪除
    void deleteEmpById(Integer id);
    //修改
    void updateEmp(Employee employee);
}

2) 而後在sql映射文件EmployeeMapper.xml中編寫相應的sql語句

<!--parameterType:參數類型,能夠省略-->
    <insert id="addEmp" parameterType="employee">
        insert into tbl_employee (last_name, gender, email) value (#{lastName},#{gender},#{email})
    </insert>
    
    <delete id="deleteEmpById">
        delete from tbl_employee where id = #{id}
    </delete>

    <update id="updateEmp">
        update tbl_employee set last_name=#{lastName},email=#{email},gender=#{gender}
        where id = #{id}
    </update>

3) 測試:

public void test2() throws IOException {
    SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
    //默認不自動提交事務,須要手動提交,或者構造方法傳入true
    SqlSession sqlSession = sqlSessionFactory.openSession();
    try {
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        //增
        mapper.addEmp(new Employee(4,"林青霞","lingqingxia@163.com","1"));
        //刪
        mapper.deleteEmpById(2);
        //改
        mapper.updateEmp(new Employee(1,"張柏芝","zhangbozhi@qq.com","1"));
        // 手動提交事務
        sqlSession.commit();
    } finally {
        sqlSession.close();
    }
}

注意事項:增刪改須要提交事務
sqlSessionFactory.openSession():須要手動提交, sqlSession.commit();
sqlSessionFactory.openSession(true)是自動提交事務的

5.2 獲取自增主鍵的值

MySQL支持自增主鍵,在MyBatis中也是使用statement.getGeneratedKeys()獲取自增主鍵的值。
用法,insert標籤中:

  • useGeneratedKeys="true":使用自增主鍵獲取主鍵值策略
  • keyProperty="id":獲取到的主鍵封裝給JavaBean的id屬性
<!--useGeneratedKeys="true":使用自增主鍵獲取主鍵值策略-->
<!--keyProperty="id":獲取到的主鍵封裝給JavaBean的id屬性-->
<insert id="addEmp" parameterType="employee" useGeneratedKeys="true" keyProperty="id">
    insert into tbl_employee (last_name, gender, email) value (#{lastName},#{gender},#{email})
</insert>

測試:employee.getId()方法能夠獲取到自增的主鍵值

Employee employee = new Employee(null,"Tom","tom@126.com","0");
mapper.addEmp(employee);
System.out.println(employee.getId());

5.3 參數處理

取參數的方式:#{參數名}

01 單個參數

MyBatis不會作處理,傳入id,sql語句中寫 #{idabc} 也能取到參數

02 多個參數

會被封裝成一個map,#{}就是從map中獲取指定key值的value。
key:param1,...paramN,或者參數的索引也能夠
value:傳入的參數值

若是按照以前的寫法會報異常:

Employee employee = mapper.selectEmpByIdAndName(6, "Tom");

<select id="selectEmpByIdAndName" resultType="employee">
      select  * from tbl_employee where id = #{id} and last_name=#{lastName}
</select>

BindingException: Parameter 'id' not found. Available parameters are [arg1, arg0, param1, param2]

正確寫法,可是通常不這麼用:

select  * from tbl_employee where id = #{param1} and last_name=#{param2}

常規用法:在接口方法中加註解@Param至關於key中保存的是@Param註解指定的值

Employee selectEmpByIdAndName(@Param("id") Integer id, @Param("lastName") String lastName);

03 傳入對象

  • 若是多個參數正好是業務邏輯的數據模型,就能夠直接傳入POJO,#{屬性名}取出傳入的POJO的屬性值
  • 若是不是業務模型中的數據,沒有對應的POJO,也能夠傳入map,#{key}取出map中對應的值,或者編寫一個TO(Transfer Object )數據傳輸對象。
//封裝傳入參數爲Map,查詢
Employee selectEmpByMap(Map<String,Object> map);
HashMap<String,Object> map = new HashMap<>();
//注意sql語句中#{id},#{lastName}與map中的鍵字段名稱一一對應
map.put("id",6);
map.put("lastName","Tom");
Employee employee = mapper.selectEmpByMap(map);
System.out.println(employee);
select  * from tbl_employee where id = #{id} and last_name=#{lastName}

04 幾種狀況小結

  1. Employee getEmp(@Param("id") Integer id, String lastName)

    取值:id ==> #{id}或者#{param1},lastName ==> #{param2}

  2. Employee getEmp(Integer id, @Param("e") Employee emp)

    取值:id ==> #{param1},lastName ==> #{e.lastName}或者 #{param2.lastName}

  3. 若是傳入的是Collection類型或者是數組,則會把傳入的集合或者數組封裝在map中

    key:collection,list,array

    Employee getEmp(List<Integer> ids)

    取值:取出第一個id的值 #{list[0]}

總結:參數多時封裝成map,結合@Param指定封裝時使用的key,#{key}取出map中的值

05 #{}和${}的區別

#{}:是以預編譯的形式,即佔位符,將參數設置到sql語句中,相似於JDBC中的PreparedStament,防止sql注入

${}:取出的值直接拼裝在sql語句中,會有安全問題。

原生JDBC中不支持佔位符的地方就能夠用${取值},好比表名、排序字段等

select * from ${year}_salary where ...

select * from tbl_employee order by ${name}

5.4 select的結果映射

01 返回的是集合

select語句中resultType寫的是集合中元素的類型

//查詢全部,返回一個集合
List<Employee> selectAll();
<select id="selectAll" resultType="employee">
    select  * from tbl_employee
</select>

測試:

List<Employee> employees = mapper.selectAll();
for (Employee employee : employees) {
    System.out.println(employee);
}

結果:

Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}
Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}
Employee{id=5, lastName='林青霞', email='lingqingxia@163.com', gender='1'}
Employee{id=6, lastName='Tom', email='tom@126.com', gender='0'}

02 返回一條記錄的map

若是想把一條記錄的返回值類型封裝爲map,key值爲字段名,value值。則指定resultType爲map,查詢到的結果會將指定的列映射到Map 的鍵上,結果映射到值上。

//返回一條記錄的map,key是列名,值就是對應的值
Map<String,Object> selectEmpByIdToMap(Integer id);
<!--查詢並返回一條記錄的map-->
    <select id="selectEmpByIdToMap" resultType="map">
        select * from tbl_employee where id = #{id}
    </select>

測試:

Map<String, Object> map = mapper.selectEmpByIdToMap(1);
System.out.println(map);

結果:

{gender=1, last_name=張曼玉, id=1, email=zhangmanyu@163.com}

03 多條記錄封裝爲map

若是想封裝多條記錄到map中,key是主鍵值,value是JavaBean對象,則在接口方法上使用@MapKey註解指定key的屬性; resultType依然爲map中的value類型

//返回多條記錄封裝到map中,key是主鍵值,value是JavaBean對象
//@MapKey;指定返回的map的key
@MapKey("id")
Map<Integer,Employee> selectAllToMap();
<!-- 返回多條記錄封裝到map中,key是主鍵值,value是JavaBean對象-->
    <select id="selectAllToMap" resultType="employee">
        select *
        from tbl_employee
    </select>

測試:

Map<Integer, Employee> map = mapper.selectAllToMap();
System.out.println(map);

結果:

{1=Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}, 3=Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}, 5=Employee{id=5, lastName='林青霞', email='lingqingxia@163.com', gender='1'}, 6=Employee{id=6, lastName='Tom', email='tom@126.com', gender='0'}}

而指定 resultType 屬性爲實體類時,查詢到的結果會將指定的列映射到類的成員變量名上,結果映射到成員變量值上。

【注意】:實體類中的屬性名和數據庫中的字段名保持一致。不然結果值會爲null。

若是列名和屬性名不能匹配上,且不知足駝峯命名自動映射,能夠在 SELECT 語句中設置列別名,也能夠顯式配置 ResultMap

04 自定義ResultMap

若是列名和屬性名不能匹配上,在xml映射文件中顯式配置 ResultMap標籤

  • type:自定義規則的Java類型
  • id:惟一id,用於引用
  • column:指定數據庫中的字段
  • property:指定對應的JavaBean中的屬性
<!--自定義結果集規則-->
<!--type:自定義規則的Java類型-->
<!--id:惟一id,用於引用-->
<resultMap id="MyEmp" type="employee">
    <!--用id標籤訂義主鍵底層會有優化,普通列用result標籤-->
    <!--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取代resultType,值爲自命名的id-->
<select id="selectEmp" resultMap="MyEmp">
    select *
    from tbl_employee
    where id = #{id}
</select>

6. association--多對一查詢

處理多表查詢。
MyBatis 有兩種不一樣的方式加載關聯:

  • 嵌套 Select 查詢:經過執行另一個 SQL 映射語句來加載指望的複雜類型。
  • 嵌套結果映射:使用嵌套的結果映射來處理鏈接結果的重複子集。

6.1 測試環境

兩張表,員工表tbl_employee和部門表tbl_dept,員工表中設置外鍵部門id,指向部門表的主鍵
在以前的基礎上新建了部門表,並在員工表中設置外鍵,插入數據:

CREATE TABLE tbl_dept(
id INT(10) PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(255)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;

ALTER TABLE tbl_employee ADD COLUMN d_id INT(10);
ALTER TABLE tbl_employee ADD CONSTRAINT fk_emp_dept FOREIGN KEY(d_id) REFERENCES tbl_dept(id);

員工表:

image-20200612181534452.png

部門表:
image-20200612181552644.png

Employee實體類:

public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private String gender;
    private Dept dept;
    //getter/setter...
}

Dept實體類:

public class Dept {
    private Integer id;
    private String departmentName;
    //getter/setter...
}

6.2 級聯屬性封裝結果

在接口文件中聲明一個根據員工id查詢員工信息的方法,由於員工信息中一個成員是部門對象,按照以前的映射關係,沒法查詢部門信息。

public interface EmployeeMapper {
    //根據id查詢員工的信息及其部門信息
    Employee getEmpAndDept(Integer id);
}

採用級聯屬性來封裝結果:
employee中有一個成員是dept,由dept.id和dept.departmentName則能夠獲取到部門的id和部門名稱

<!--聯合查詢,級聯屬性封裝結果集-->
<resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="email" property="email"/>
    <result column="did" property="dept.id"/>
    <result column="dept_name" property="dept.departmentName"/>
</resultMap>

<select id="getEmpAndDept"  resultMap="MyDifEmp ">
    <!--給列起別名-->
    select e.id id, e.last_name last_name,e.gender gender,e.email email,
           d.id did,d.dept_name dept_name  from tbl_employee  e,tbl_dept d
    where e.d_id = d.id and e.id = #{id}
</select>

測試:

Employee employee = mapper.getEmpAndDept(1);
System.out.println(employee);

結果:重點是執行的sql語句

DEBUG 06-12 20:16:45,106 ==>  Preparing: select e.id id, e.last_name last_name,e.gender gender,e.email email, d.id did,d.dept_name dept_name from tbl_employee e,tbl_dept d where e.d_id = d.id and e.id = ?   (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:16:45,213 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:16:45,253 <==      Total: 1  (BaseJdbcLogger.java:143) 
Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1', dept=Dept{id=1, departmentName='開發部'}}

6.3 association嵌套結果集

關聯(association)元素能夠處理多表查詢。
MyBatis 有兩種不一樣的方式加載關聯:

  • 嵌套結果映射:使用嵌套的結果映射來處理鏈接結果的重複子集。
  • 嵌套 Select 查詢:即分佈查詢,經過執行另一個 SQL 映射語句來加載指望的複雜類型。

查詢結果本質上是Employee類,其中的成員Dept是對象,須要進行關聯association,用javaType獲取屬性的類型。兩種方式結果相同。

<!--查詢結果本質上是Employee類-->
<resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
     <!--對查詢結果進行映射-->
    <!--數據庫中的字段取了別名後用別名-->
    <id column="id" property="id"/>
    <result column="last_name" property="lastName"/>
    <result column="gender" property="gender"/>
    <result column="email" property="email"/>

    <!-- dept成員是一個對象,須要用association指定聯合的JavaBean對象-->
    <!-- property="dept":指定哪一個屬性是聯合的對象-->
    <!-- javaType:指定這個屬性對象的類型,不能省略-->
    <association property="dept" javaType="com.xiao.pojo.Dept">
        <id column="did" property="id"/>
        <id column="dept_name" property="departmentName"/>
    </association>
</resultMap>

<select id="getEmpAndDept"  resultMap="MyDifEmp">
    <!--給列起別名-->
    select e.id id, e.last_name last_name,e.gender gender,e.email email,
           d.id did,d.dept_name dept_name  from tbl_employee  e,tbl_dept d
    where e.d_id = d.id and e.id = #{id}
</select>

6.4 association分步查詢

思路:

  1. 先根據輸入的員工id查詢員工信息
  2. 再根據查詢到的員工信息中的d_id,查詢對應的部門信息
  3. 將部門信息設置給員工
<!--association分步查詢-->
 <!--第一步,先查詢員工信息-->
 <!--第二步,根據查詢到的員工信息的did,尋找對應的部門信息-->
 <select id="getEmpAndDept" resultMap="MyDifEmp">
     <!--第一步,先查詢員工信息-->
     select id, last_name, gender,email,d_id from tbl_employee  where id = #{id}
 </select>
 <resultMap id="MyDifEmp" type="com.xiao.pojo.Employee">
     <!--簡單的屬性直接寫-->
     <id column="id" property="id"/>
     <result column="last_name" property="lastName"/>
     <result column="gender" property="gender"/>
     <result column="email" property="email"/>
     <!--複雜的屬性,須要單獨處理-->
     <!--select:調用指定的方法查出結果-->
     <!--column:將哪一列的值傳給這個方法-->
     <!--property:查出的結果封裝給property指定的屬性-->
    <association property="dept" column="d_id" javaType="com.xiao.pojo.Dept" select="getDept"/>

</resultMap>
<!--第二步,根據查詢到的員工信息中的d_id,查詢對應的部門信息-->
<!--能夠寫到部門的sql映射文件中,這裏簡略了-->
<select id="getDept" resultType="com.xiao.pojo.Dept">
    select id,dept_name departmentName from tbl_dept where id = #{d_id}
</select>

association標籤中的屬性:

  • select:調用指定的方法查出結果
  • column:將哪一列的值傳給這個方法
  • property:查出的結果封裝給property指定的屬性

執行流程:使用select指定的方法,用傳入的column指定的這列參數的值,查出對象,並封裝給property屬性

結果:有兩條sql語句

DEBUG 06-12 20:04:49,831 ==>  Preparing: select id, last_name, gender,email,d_id from tbl_employee where id = ?   (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:04:49,945 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:04:50,006 ====>  Preparing: select id,dept_name departmentName from tbl_dept where id = ?   (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:04:50,008 ====> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:04:50,015 <====      Total: 1  (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:04:50,017 <==      Total: 1  (BaseJdbcLogger.java:143) 
Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1', dept=Dept{id=1, departmentName='開發部'}}

分步查詢可使用延遲加載

延遲加載(懶加載、按需加載),須要在全局配置文件中開啓

  • lazyLoadingEnabled:延遲加載的全局開關。當開啓時,全部關聯對象都會延遲加載。 特定關聯關係中可經過設置 fetchType 屬性來覆蓋該項的開關狀態。默認是false
  • aggressiveLazyLoading:開啓時,任一方法的調用都會加載該對象的全部延遲加載屬性。 不然,每一個延遲加載屬性會按需加載。(在 3.4.1 及以前的版本中默認爲 true,以後默認爲false)
<settings>
    <!--開啓懶加載-->
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>  
</settings>

測試中只使用查詢到的員工信息,不使用員工信息,則只會發出一條sql語句,若是不開啓延遲加載,則老是會發出兩條sql語句:

Employee employee = mapper.getEmpAndDept(1);
System.out.println(employee.getEmail());

結果,只發出了第一步的sql語句:

DEBUG 06-12 20:31:55,182 ==>  Preparing: select id, last_name, gender,email,d_id from tbl_employee where id = ?   (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:31:55,290 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 06-12 20:31:55,516 <==      Total: 1  (BaseJdbcLogger.java:143) 
zhangmanyu@163.com

7 collection--一對多查詢

查詢部門,並講部門對應的全部員工信息查詢出來

public class Employee {
    private Integer id;
    private String lastName;
    private String email;
    private String gender;
   //getter/setter...
}

Dept實體類:

public class Dept {
    private Integer id;
    private String departmentName;
    private List<Employee> emps;
    //getter/setter...
}

DeptMapper接口文件:

public interface DeptMapper {
    Dept selectDeptById(Integer id);
}

7.1 嵌套結果集

使用關聯查詢,根據Dept的id查詢Dept信息,其中Dept中有一個成員是emps,即員工的集合,須要用到collection標籤進行結果映射。

集合標籤:collection

  • property:指定哪一個屬性是集合
  • ofType:獲取集合中的泛型信息

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">
<mapper namespace="com.xiao.dao.DeptMapper">

    <!--嵌套結果查詢-->
    <select id="selectDeptById" resultMap="MyDept">
        select d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender
        from tbl_dept d
        left join tbl_employee e
        on d.id = e.d_id
        where d.id = #{id}
    </select>

    <resultMap id="MyDept" type="com.xiao.pojo.Dept">
        <id column="did" property="id"/>
        <result column="dept_name" property="departmentName"/>
        <!--複雜的屬性,單獨處理,集合:collection,property指定哪一個屬性是集合,用ofType獲取集合中的泛型信息-->
        <collection property="emps" ofType="com.xiao.pojo.Employee">
            <result column="eid" property="id"/>
            <result column="last_name" property="lastName"/>
            <result column="email" property="email"/>
            <result column="gender" property="gender"/>
        </collection>
    </resultMap>

</mapper>

測試:

Dept dept = mapper.selectDeptById(1);
System.out.println(dept);

查詢結果,一個部門中有兩個員工信息:

DEBUG 06-12 21:24:02,595 ==>  Preparing: select d.id did, d.dept_name dept_name, e.id eid, e.last_name last_name, e.email email, e.gender gender from tbl_dept d left join tbl_employee e on d.id = e.d_id where d.id = ?   (BaseJdbcLogger.java:143) 
DEBUG 06-12 21:24:02,675 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:143) 
DEBUG 06-12 21:24:02,721 <==      Total: 2  (BaseJdbcLogger.java:143) 
Dept{id=1, departmentName='開發部', emps=[Employee{id=1, lastName='張曼玉', email='zhangmanyu@163.com', gender='1'}, Employee{id=3, lastName='wangwu', email='wangwu@126.com', gender='0'}]}

7.2 分步查詢

思路與多對一中的思路類型:

  1. 先根據輸入的部門id查詢部門信息
  2. 再根據查詢到的部門信息中的部門id,查詢對應的員工信息
  3. 將員工信息設置給部門
<!--分步查詢-->
<select id="selectDeptById" resultMap="MyDept">
    select id, dept_name
    from tbl_dept
    where id = #{id}
</select>

<resultMap id="MyDept" type="com.xiao.pojo.Dept">
    <id column="id" property="id"/>
    <result column="dept_name" property="departmentName"/>
    <collection property="emps" ofType="com.xiao.pojo.Employee" select="getEmp" column="id"/>
</resultMap>

<select id="getEmp" resultType="com.xiao.pojo.Employee">
    select id,last_name lastName,email,gender from tbl_employee where d_id = #{id}
</select>

7.3 補充幾個小點

  1. 分步查詢如何傳遞多列的值?

    將多列的值封裝成map傳遞:column={key1=column1,key2=column2...},使用時則用#{key}獲取

  2. 開啓了全局懶加載後,特定關聯關係中可經過設置 fetchType 屬性來覆蓋該項的開關狀態

    • fetchType="lazy":懶加載
    • fetchType="eager":當即加載
  3. 鑑別器discriminator

    相似於 Java 語言中的 switch 語句,能夠根據不一樣的條件封裝不一樣的結果集。須要指定 column 和 javaType 屬性。

    • javaType:列值對應的java類型
    • column:指定用於斷定的列名
    • value:進行條件的判斷,知足該條件,則選擇該結果封裝規則
<discriminator javaType="string" column="gender">
  <case value="0" resultType="com.xiao.pojo.Employee">
      ....
  </case>
  <case value="0" resultType="com.xiao.pojo.Employee">
      ....
  </case>
</discriminator>
相關文章
相關標籤/搜索