Mybatis實戰

一、前言

多數的ORM框架都把【增加】、【修改】與【刪除】做得非常不錯了,然而數據庫中【查詢】無疑是使用頻次最高、複雜度大、與性能密切相關的操作,我們希望得到一種使用方便,查詢靈活的ORM框架,MyBatis可以滿足這些要求,MyBatis是一個支持普通SQL查詢,存儲過程和高級映射的優秀持久層框架,它也是SSM(springmvc+spring+mybatis)框架集成中的重要組成部分。

1.1、ORM

ORM可以解決數據庫與程序間的異構性,比如在Java中我們使用String表示字符串,而Oracle中可使用varchar2,MySQL中可使用varchar,SQLServer可使用nvarchar。

對象關係映射(英語:Object Relational Mapping,簡稱ORM,或O/RM,或O/R mapping),用於實現面向對象編程語言裏不同類型系統的數據之間的轉換

簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據,將程序中的對象與關係數據庫相互映射。

沒有ORM時我們是這樣完成對象與關係數據庫之間的映射的:

            //將執行的sql
            String sql = "SELECT name, id, age, password FROM users";
            //創建命令對象
            preparedStatement = connection.prepareStatement(sql);
            //執行並獲得結果集
            resultSet = preparedStatement.executeQuery();
            //遍歷結果集,將數據庫中的數據轉換成Java中的對象
            while(resultSet.next()){
                String name = resultSet.getString("name");
                int id = resultSet.getInt("id");
                int age = resultSet.getInt("age");
                String password = resultSet.getString("password");
                User entity= new User(name,id,age,password);
                Users.add(entity);
            }

這種方案存在以下不足: 

  • 持久化層缺乏彈性。一旦出現業務需求的變更,就必須修改持久化層的接口
  • 持久化層同時與域模型與關係數據庫模型綁定,不管域模型還是關係數據庫模型發生變化,都要修改持久化曾的相關程序代碼,增加了軟件的維護難度。
  • 將和數據庫交互(CRUD)的代碼硬編碼到JDBC程序中
  • 實現健壯的持久化層需要高超的開發技巧,而且編程量很大(因爲面向的是JDBC,偏底層)
  • 對象模型和關係模型的轉換非常麻煩

ORM可以解決這些問題:

  • 一種將內存中的對象保存到關係型數據庫中的技術
  • 負責實體域對象的持久化,封裝數據庫訪問細節
  • ORM提供了實現持久化層的另一種模式,採用映射元數據(XML)來描述對象-關係的映射細節,使得ORM中間件能在任何一個Java應用的業務邏輯層和數據庫之間充當橋樑。
  • ORM提供了實現持久化層的另一種模式,它採用映射元數據來描述對象關係的映射,使得ORM中間件能在任何一個應用的業務邏輯層和數據庫層之間充當橋樑。

1.2、Java領域典型的ORM

  • hibernate:全自動的框架,強大、複雜、笨重、學習成本較高
  • Mybatis:半自動的框架(懂數據庫的人 才能操作,必須要自己寫sql)
  • JPA:JPA全稱Java Persistence API、JPA通過JDK 5.0註解或XML描述對象-關係表的映射關係,是Java自帶的框架

1.3、ORM概念

ORM解決的主要問題是對象關係的映射。域模型和關係模型分別是建立在概念模型的基礎上的。域模型是面向對象的,而關係模型是面向關係的。一般情況下,一個持久化類和一個表對應,類的每個實例對應表中的一條記錄,類的每個屬性對應表的每個字段。因此ORM的目的是爲了方便開發人員以面向對象的思想來實現對數據庫的操作。

面向對象概念 面向關係概念
對象 表的行(記錄)
屬性 表的列(字段)

1.4、ORM優缺點

優點

  • 提高了開發效率。由於ORM可以自動對Entity對象與數據庫中的Table進行字段與屬性的映射,所以我們實際可能已經不需要一個專用的、龐大的數據訪問層。
  • ORM提供了對數據庫的映射,不用sql直接編碼,能夠像操作對象一樣從數據庫獲取數據。

缺點

  • 犧牲程序的執行效率和會固定思維模式,降低了開發的靈活性。
  • 從系統結構上來看,採用ORM的系統一般都是多層系統,系統的層次多了,效率就會降低。ORM是一種完全的面向對象的做法,而面向對象的做法也會對性能產生一定的影響。
  • 在我們開發系統時,一般都有性能問題。性能問題主要產生在算法不正確和與數據庫不正確的使用上。ORM所生成的代碼一般不太可能寫出很高效的算法,在數據庫應用上更有可能會被誤用,主要體現在對持久對象的提取和和數據的加工處理上,如果用上了ORM,程序員很有可能將全部的數據提取到內存對象中,然後再進行過濾和加工處理,這樣就容易產生性能問題。
  • 在對對象做持久化時,ORM一般會持久化所有的屬性,有時,這是不希望的。
  • 但ORM是一種工具,工具確實能解決一些重複,簡單的勞動。這是不可否認的。但我們不能指望工具能一勞永逸的解決所有問題,有些問題還是需要特殊處理的,但需要特殊處理的部分對絕大多數的系統,應該是很少的。

二、Mybatis

MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名爲MyBatis 。2013年11月遷移到Github。

iBATIS一詞來源於“internet”和“abatis”的組合,是一個基於Java的持久層框架。iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAOs)

2.1、MyBatis的特點

  • 簡單易學:本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件易於學習,易於使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實現。
  • 靈活:mybatis不會對應用程序或者數據庫的現有設計強加任何影響。 sql寫在xml裏,便於統一管理和優化。通過sql基本上可以實現我們不使用數據訪問框架可以實現的所有功能,或許更多。
  • 解除sql與程序代碼的耦合:通過提供DAO層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。
  • 提供映射標籤,支持對象與數據庫的ORM字段關係映射
  • 提供對象關係映射標籤,支持對象關係組建維護
  • 提供XML標籤,支持編寫動態sql。

2.2、MyBatis工作流程

(1)、加載配置並初始化

觸發條件:加載配置文件

配置來源於兩個地方,一處是配置文件,一處是Java代碼的註解,將SQL的配置信息加載成爲一個個MappedStatement對象(包括了傳入參數映射配置、執行的SQL語句、結果映射配置),存儲在內存中。

(2)、接收調用請求

觸發條件:調用Mybatis提供的API

傳入參數:爲SQL的ID和傳入參數對象

處理過程:將請求傳遞給下層的請求處理層進行處理。

(3)、處理操作請求 觸發條件:API接口層傳遞請求過來 

傳入參數:爲SQL的ID和傳入參數對象

處理過程:
  (A)根據SQL的ID查找對應的MappedStatement對象。

  (B)根據傳入參數對象解析MappedStatement對象,得到最終要執行的SQL和執行傳入參數。

  (C)獲取數據庫連接,根據得到的最終SQL語句和執行傳入參數到數據庫執行,並得到執行結果。

  (D)根據MappedStatement對象中的結果映射配置對得到的執行結果進行轉換處理,並得到最終的處理結果。

  (E)釋放連接資源。

(4)、返回處理結果將最終的處理結果返回。

無論是用過的hibernate,mybatis,你都可以發現他們有一個共同點:

在java對象和數據庫之間有做mapping的配置文件,也通常是xml 文件

從配置文件(通常是XML配置文件中)得到 SessionFactory

由SessionFactory 產生 Session

在Session中完成對數據的增刪改查和事務提交等

在用完之後關閉Session

2.3、MyBatis架構

Mybatis的功能架構分爲三層:

  • API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
  • 數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。
  • 基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作爲最基礎的組件。爲上層的數據處理層提供最基礎的支撐。

2.4、MyBatis的主要成員及層次結構

Configuration MyBatis所有的配置信息都保存在Configuration對象之中,配置文件中的大部分配置都會存儲到該類中
SqlSession 作爲MyBatis工作的主要頂層API,表示和數據庫交互時的會話,完成必要數據庫增刪改查功能
Executor MyBatis執行器,是MyBatis 調度的核心,負責SQL語句的生成和查詢緩存的維護
StatementHandler 封裝了JDBC Statement操作,負責對JDBC statement 的操作,如設置參數等
ParameterHandler 負責對用戶傳遞的參數轉換成JDBC Statement 所對應的數據類型
ResultSetHandler 負責將JDBC返回的ResultSet結果集對象轉換成List類型的集合
TypeHandler 負責java數據類型和jdbc數據類型(也可以說是數據表列類型)之間的映射和轉換
MappedStatement MappedStatement維護一條<select|update|delete|insert>節點的封裝
SqlSource 負責根據用戶傳遞的parameterObject,動態地生成SQL語句,將信息封裝到BoundSql對象中,並返回
BoundSql 表示動態生成的SQL語句以及相應的參數信息

2.5、學習資源

mybatis3中文幫助:http://www.mybatis.org/mybatis-3/zh/index.html

mybatis-spring:http://www.mybatis.org/spring/zh/index.html

MyBatis中國分站:http://www.mybatis.cn/

源代碼:https://github.com/mybatis/mybatis-3/

三、MyBatis快速入門示例

3.1、在IDEA中創建普通maven項目

3.2、添加依賴

下載地址:https://github.com/mybatis/mybatis-3/releases

如果是maven項目,pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>

<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>com.mzj.mybatis</groupId>
  <artifactId>mybatis-demo</artifactId>
  <version>1.0-SNAPSHOT</version>

  <name>mybatis-demo</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
  </properties>

  <dependencies>
    <!--MyBatis -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.4.6</version>
    </dependency>
    <!--MySql數據庫驅動 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.38</version>
    </dependency>
    <!-- JUnit單元測試工具 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.10</version>
    </dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

3.3、創建數據庫和表,針對MySQL數據庫

SQL腳本如下:

CREATE TABLE `student` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) NOT NULL,
  `sex` enum('boy','girl','secret') DEFAULT 'secret',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

插入測試數據:

INSERT INTO `student` VALUES ('1', 'rose', 'girl');
INSERT INTO `student` VALUES ('2', 'jack', 'boy');
INSERT INTO `student` VALUES ('3', 'lili', 'girl');
INSERT INTO `student` VALUES ('4', 'candy', 'secret');

3.4、添加Mybatis配置文件

在Resources目錄下創建一個conf.xml文件,如下圖所示:

conf.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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/world"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/studentMapper.xml"/>
    </mappers>
</configuration>

說明如下:

3.5、定義表所對應的實體類

package com.mzj.mybatis.entitles;

/**student表對應的實體類
 *
 * @Auther: mazhongjia
 * @Date: 2020/6/6 10:44
 * @Version: 1.0
 */
public class Student {
    private int id;
    private String name;
    private String sex;

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
}

3.6、定義操作Student表的sql映射文件

在resources目錄下創建一個mapper目錄,專門用於存放sql映射文件,在目錄中創建一個studentMapper.xml文件,如下圖所示:

內容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 爲這個mapper指定一個唯一的namespace,namespace的值習慣上設置成包名+sql映射文件名,這樣就能夠保證namespace的值是唯一的
例如namespace="com.zhangguo.mybatis01.dao.studentMapper"就是com.zhangguo.mybatis01.dao(包名)+studentMapper(studentMapper.xml文件去除後綴)
-->
<mapper namespace="com.mzj.mybatis.dao.studentMapper">
    <!-- 在select標籤中編寫查詢的SQL語句, 設置select標籤的id屬性爲selectStudentById,id屬性值必須是唯一的,不能夠重複
 使用parameterType屬性指明查詢時使用的參數類型,resultType屬性指明查詢返回的結果集類型
resultType="com.zhangguo.mybatis01.entities.Student"就表示將查詢結果封裝成一個Student類的對象返回
Student類就是student表所對應的實體類
-->
    <!--
    根據id查詢得到一個user對象
 -->
    <select id="selectStudentById" resultType="com.mzj.mybatis.entitles.Student">
        select * from student where id = #{id}
    </select>
</mapper>

3.7、在配置文件中註冊映射文件

在配置文件conf.xml中註冊studentMapper.xml映射文件

3.8、編寫數據訪問類

StudentDao.java,執行定義的select語句

package com.mzj.mybatis.dao;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/6 10:56
 * @Version: 1.0
 */
import com.mzj.mybatis.entitles.Student;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;

public class StudentDao {

    public Student getStudentById(int id){
        //1、使用類加載器加載mybatis的配置文件(它也加載關聯的映射文件)
        InputStream stream=StudentDao.class.getClassLoader().getResourceAsStream("conf.xml");
        //2、構建SqlSessionFactory工廠
        SqlSessionFactory ssf=new SqlSessionFactoryBuilder().build(stream);
        //使用MyBatis提供的Resources類加載mybatis的配置文件(它也加載關聯的映射文件)
        //Reader reader = Resources.getResourceAsReader(resource);
        //構建sqlSession的工廠
        //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);

        //3、創建能執行映射文件中sql的sqlSession
        SqlSession session=ssf.openSession();
        /**
         * 映射sql的標識字符串,
         * com.mzj.mybatis.dao.studentMapper是studentMapper.xml文件中mapper標籤的namespace屬性的值,
         * selectStudentById是select標籤的id屬性值,通過select標籤的id屬性值就可以找到要執行的SQL
         */
        Student student=session.selectOne("com.mzj.mybatis.dao.studentMapper.selectStudentById",1);
        return student;
    }

 
}

  • mybatis配置
    • SqlMapConfig.xml(本例爲conf.xml),此文件作爲mybatis的全局配置文件,配置了mybatis的運行環境等信息
    • mapper.xml文件即sql映射文件,文件中配置了操作數據庫的sql語句。此文件需要在SqlMapConfig.xml中加載。
  • 通過mybatis環境等配置信息構造SqlSessionFactory即會話工廠
  • 由會話工廠創建sqlSession即會話,操作數據庫需要通過sqlSession進行。
  • mybatis底層自定義了Executor執行器接口操作數據庫,Executor接口有兩個實現,一個是基本執行器、一個是緩存執行器。
  • Mapped Statement也是mybatis一個底層封裝對象,它包裝了mybatis配置信息及sql映射信息等。mapper.xml文件中一個sql對應一個Mapped Statement對象,sql的id即是Mapped statement的id。
  • Mapped Statement對sql執行輸入參數進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql前將輸入的java對象映射至sql中,輸入參數映射就是jdbc編程中對preparedStatement設置參數。
  • Mapped Statement對sql執行輸出結果進行定義,包括HashMap、基本類型、pojo,Executor通過Mapped Statement在執行sql後將輸出結果映射至java對象中,輸出結果映射過程相當於jdbc編程中對結果的解析處理過程。

3.9、編寫單元測試

package com.mzj.mybatis.dao;

import com.mzj.mybatis.entitles.Student;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/6 11:10
 * @Version: 1.0
 */
public class StudentDaoTest {

    @Before
    public void before() throws Exception {
    }

    @After
    public void after() throws Exception {
    }

    /**
     * Method: getStudentById(int id)
     */
    @Test
    public void testGetStudentById() throws Exception {
        StudentDao dao=new StudentDao();
        Student student=dao.getStudentById(1);
        System.out.println(student);
    }


}

運行:

四、基於XML映射實現完整數據訪問

mybatis可以使用註解或者XML,兩種方式進行配置。

XML:強大且可以解偶。

註解:方便且簡單

因爲每一個操作都需要先拿到會話,這裏先定義一個工具類以便複用:

package com.mzj.mybatis.utils;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/10 12:23
 * @Version: 1.0
 */

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * MyBatis 會話工具類
 */
public class SqlSessionFactoryUtil {

    /**
     * 獲得會話工廠
     */
    public static SqlSessionFactory getFactory() {
        InputStream inputStream = null;
        SqlSessionFactory sqlSessionFactory = null;

        try {
            //加載conf.xml配置文件,轉換成輸入流
            inputStream = SqlSessionFactoryUtil.class.getClassLoader().getResourceAsStream("conf.xml");

            //根據配置文件的輸入流構造一個SQL會話工廠
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return sqlSessionFactory;
    }

    /**
     * 獲得sql會話,是否自動提交
     */
    public static SqlSession openSession(boolean isAutoCommit) {
        return getFactory().openSession(isAutoCommit);
    }

    /**
     * 關閉會話
     */
    public static void closeSession(SqlSession session) {
        if (session != null) {
            session.close();
        }
    }

}

mybatis的業務mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mzj.mybatis.dao.studentMapperFull">
    <select id="selectStudentById" resultType="com.mzj.mybatis.entitles.Student">
        SELECT id,name,sex from student where id=#{id}
    </select>

    <select id="selectStudentsByName" parameterType="String" resultType="com.mzj.mybatis.entitles.Student">
      SELECT id,name,sex from student where name like '%${value}%';
    </select>

    <insert id="insertStudent" parameterType="com.mzj.mybatis.entitles.Student">
        insert into student(name,sex) VALUES(#{name},'${sex}')
    </insert>

    <update id="updateStudent" parameterType="com.mzj.mybatis.entitles.Student">
        update student set name=#{name},sex=#{sex} where id=#{id}
    </update>

    <delete id="deleteStudent" parameterType="int">
        delete from student where id=#{id}
    </delete>

</mapper>

mybatis配置文件增加業務mapper.xml

業務DAO

package com.mzj.mybatis.dao;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/10 12:27
 * @Version: 1.0
 */

import com.mzj.mybatis.entitles.Student;
import com.mzj.mybatis.utils.SqlSessionFactoryUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class StudentDaoFull {

    /**
     * 根據學生編號獲得學生對象
     */
    public Student selectStudentById(int id) {
        Student entity = null;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil.openSession(true);

        //查詢單個對象,指定參數爲3
        entity = session.selectOne("com.mzj.mybatis.dao.studentMapperFull.selectStudentById", id);

        //關閉
        SqlSessionFactoryUtil.closeSession(session);

        return entity;
    }


    /**
     * 根據學生姓名獲得學生集合
     */
    public List<Student> selectStudentsByName(String name) {
        List<Student> entities = null;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil.openSession(true);
        //查詢多個對象,指定參數
        entities = session.selectList("com.mzj.mybatis.dao.studentMapperFull.selectStudentsByName", name);
        //關閉
        SqlSessionFactoryUtil.closeSession(session);
        return entities;
    }


    /**
     * 添加學生
     */
    public int insertStudent(Student entity) {
        //影響行數
        int rows = 0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil.openSession(true);
        //執行添加
        rows = session.insert("com.mzj.mybatis.dao.studentMapperFull.insertStudent", entity);
        //關閉
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

    /**
     * 更新學生
     */
    public int updateStudent(Student entity) {
        //影響行數
        int rows = 0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil.openSession(true);
        //執行更新
        rows = session.update("com.mzj.mybatis.dao.studentMapperFull.updateStudent", entity);
        //關閉
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

    /**
     * 刪除學生
     */
    public int deleteStudent(int id) {
        //影響行數
        int rows = 0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil.openSession(true);
        //執行刪除
        rows = session.delete("com.mzj.mybatis.dao.studentMapperFull.deleteStudent", id);
        //關閉
        SqlSessionFactoryUtil.closeSession(session);
        return rows;
    }

}

測試用例

package com.mzj.mybatis.dao;

import com.mzj.mybatis.entitles.Student;
import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;

import java.util.List;

/**
 * StudentDao Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>09/26/2018</pre>
 */
public class StudentDaoFullTest {

    StudentDaoFull dao;
    @Before
    public void before() throws Exception {
        dao=new StudentDaoFull();
    }

    @After
    public void after() throws Exception {
    }

    /**
     * Method: selectStudentById(int id)
     */
    @Test
    public void testSelectStudentById() throws Exception {
        Student entity=dao.selectStudentById(1);
        System.out.println(entity);
        Assert.assertNotNull(entity);
    }

    /**
     * Method: selectStudentsByName(String name)
     */
    @Test
    public void testSelectStudentsByName() throws Exception {
        List<Student> students=dao.selectStudentsByName("i");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

    /**
     * Method: insertStudent
     */
    @Test
    public void testInsertStudent() throws Exception {
        Student entity=new Student();
        entity.setName("瑪麗");
        entity.setSex("girl");

        Assert.assertEquals(1,dao.insertStudent(entity));
    }

    /**
     * Method: updateStudent
     */
    @Test
    public void testUpdateStudent() throws Exception {
        Student entity=dao.selectStudentById(3);
        entity.setName("馬力");
        entity.setSex("boy");

        Assert.assertEquals(1,dao.updateStudent(entity));
    }

    /**
     * Method: deleteStudent
     */
    @Test
    public void testDeleteStudent() throws Exception {
        Assert.assertEquals(1,dao.deleteStudent(2));
    }
}

mybatis的mapper.xml編寫說明及示例1:

<?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接口的全限定名一致 -->
<mapper namespace="com.san.mapper.UserMapper">

    <!-- 通過ID查詢用戶 -->
    <select id="findUserById" parameterType="int" resultType="user">
        select * from user where id=#{id}
    </select>

    <!-- 定義sql片段 -->
    <!-- sql片段內,可以定義sql語句中的任何內容 -->
    <!-- sql片段內,最好不要使用where和select關鍵字聲明在內 -->
    <sql id="whereClause">
        <!-- if標籤:對輸入的參數進行判斷 -->
        <!-- test標籤:指定判斷的表達式 -->
        <if test="user!=null">
            <!-- 判斷用戶名不爲空 -->
            <if test="user.username!=null and user.username!=''">
                and username like '%${user.username}%'
            </if>

            <!-- 判斷性別不爲空 -->
            <if test="user.sex!=null and user.sex!=''">
                and sex=#{user.sex}
            </if>
        </if>

        <!-- 判斷集合 -->
        <!-- collection:表示pojo中集合屬性的屬性名稱 -->
        <!-- item:爲遍歷出的結果聲明一個變量名稱 -->
        <!-- open:遍歷開始時,需要拼接的字符串 -->
        <!-- close:遍歷結束時,需要拼接的字符串 -->
        <!-- separator:遍歷中間需要拼接的字符串 -->
        <if test="idList!=null">
            and id in
            <foreach collection="idList" item="id" open="(" close=")" separator=",">
                <!-- and id in (#{id},#{id},#{id}) -->
                #{id}
            </foreach>
        </if>
    </sql>

    <!-- 綜合查詢,查詢用戶列表 -->
    <!-- #{}中的參數名稱要和包裝pojo中的對象層級一致,並且屬性名稱要一致 -->
    <select id="findUserList" parameterType="com.san.model.UserQueryvo" resultType="user">
        select * from user
        <!-- where標籤:默認去掉後面第一個and,如果沒有參數,則把自己幹掉 -->
        <where> 
            <!-- 引入sql片段 -->
            <include refid="whereClause"></include>
        </where>
    </select>

    <!-- 綜合查詢,查詢用戶的總數 -->
    <select id="findUserCount" parameterType="com.san.model.UserQueryvo" resultType="int">
        select count(*) from user 
        <where>
            <include refid="whereClause"></include>
        </where>

    </select>

    <!-- id標籤:專門爲查詢結果中唯一列映射 -->
    <!-- result標籤:映射查詢結果中的普通列 -->
    <!-- type標籤:返回類型 -->
    <resultMap type="user" id="UserResMap">
        <id column="id_" property="id"/>
        <result column="username_" property="username"/>
        <result column="sex_" property="sex"/>
    </resultMap>    
    <select id="findUserRstMap" parameterType="int" resultMap="UserResMap">
        select id id_,username username_,sex sex_ from user where id=#{id}  
    </select>
</mapper>

mybatis的mapper.xml編寫說明及示例2:

<?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命名空間,作用就是對sql進行分類化的管理,理解爲sql隔離
    注意:使用mapper代理開發時,namespace有特殊作用
 -->
<mapper namespace="test">
<!-- 在映射文件中配置很多sql語句 -->
<!-- 需求:通過Id查詢用戶表的記錄 -->
<!-- 通過SELECT執行數據庫查詢 
    id:標識映射文件中的sql,稱爲statement的id;
        將sql語句封裝在mapperStatement的對象中,所以Id稱爲Statement的id;
    parameterType:指定輸入參數的類型,這裏指定int型
    #{}:表示一個佔位符;
    #{id}:其中Id表示接收輸入的參數,參數名稱就是Id,如果輸入參數是簡單類型,#{}中的參數名可以任意,可以是value或者其它名稱;
    resultType:指定sql輸出結果所映射的java對象類型,select指定resultType表示將單條記錄映射成java對象。
-->
<select id="findUserById" parameterType="int" resultType="com.mybatis.entity.User" >
    select * from t_user where id=#{id}
</select>
<!-- 根據用戶名稱模糊查詢用戶信息,可能返回多條數據
    resultType:指定的就是單條記錄所映射的java類型;
    ${}:表示拼接sql字符串,將接收到的參數內容不加任何修飾拼接在sql中.
    使用${}拼接sql,可能會引起sql注入
    ${value}:接收輸入參數的內容,如果傳入的是簡單類型,${}中只能使用value
 -->
<select id="findUserByName" parameterType="java.lang.String" resultType="com.mybatis.entity.User" >
    select * from t_user where username LIKE '%${value}%'
</select>
<!-- 添加用戶 
parameterType:指定輸入的參數類型是pojo(包括用戶信息);
#{}中指定pojo的屬性名稱,接收到pojo對象的屬性值    ,mybatis通過OGNL(類似struts2的OGNL)獲取對象的屬性值
-->
<insert id="insertUser" parameterType="com.mybatis.entity.User" >
    <!-- 
        將insert插入的數據的主鍵返回到User對象中;
        select last_insert_id():得到剛insert進去記錄的主鍵值,只適用於自增主鍵;
        keyProperty:將查詢到的主鍵值,設置到parameterType指定的對象的那個屬性
        order:select last_insert_id()執行順序,相對於insert語句來說它的執行順序。
        resultType:指定select last_insert_id()的結果類型;
     -->
    <selectKey keyProperty="id" order="AFTER" resultType="java.lang.Integer">
        select last_insert_id()
    </selectKey>
    <!-- 
        使用mysql的uuid(),實現非自增主鍵的返回。
        執行過程:通過uuid()得到主鍵,將主鍵設置到user對象的Id的屬性中,其次,在insert執行時,從user對象中取出Id屬性值;
     <selectKey keyProperty="id" order="BEFORE" resultType="java.lang.String">
        select uuid()
    </selectKey>
        insert into t_user (id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
     -->
    insert into t_user (username,birthday,sex,address) values(#{username},#{birthday},#{sex},#{address})
</insert>
<!-- 刪除用戶 
    根據ID刪除用戶,需要輸入Id值
-->
    <delete id="deleteUser" parameterType="java.lang.Integer">
        delete from t_user where id=#{id}
    </delete>
<!-- 更新用戶 
    需要傳入用戶的Id和用戶的更新信息
    parameterType:指定User對象,包括Id和用戶的更新信息,注意:Id是必須存在的
    #{id}:從輸入的User對象中獲取Id的屬性值
-->
<update id="updateUser" parameterType="com.mybatis.entity.User">
    update t_user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} 
    where id=#{id}
</update>

</mapper>

五、基於註解映射實完整數據訪問

1、定義映射器,StudentMapper接口

package com.mzj.mybatis.dao.annotation;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/12 12:49
 * @Version: 1.0
 */
import com.mzj.mybatis.entitles.Student;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

import java.util.List;

public interface StudentMapper {
    /**
     * 根據學生編號獲得學生對象
     */
    @Select("select id,name,sex from student where id=#{id}")
    Student selectStudentById(int id);

    /**
     * 根據學生姓名獲得學生集合
     */
    @Select("SELECT id,name,sex from student where name like '%${value}%'")
    List<Student> selectStudentsByName(String name);

    /**
     * 添加學生
     */
    @Insert("insert into student(name,sex) values(#{name},#{sex})")
    int insertStudent(Student entity);

    /**
     * 更新學生
     */
    @Update("update student set name=#{name},sex=#{sex} where id=#{id}")
    int updateStudent(Student entity);

    /**
     * 刪除學生
     */
    @Delete("delete from student where id=#{id}")
    int deleteStudent(int id);
}

2、定義mybatis配置文件,其中mappers引入的是映射器,而基於XML的方式引入的是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="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/world"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--<mapper resource="mapper/studentMapper.xml"/>-->
        <mapper class="com.mzj.mybatis.dao.annotation.StudentMapper"></mapper>
    </mappers>
</configuration>

3、創建DAO類實現對student的數據訪問

通常一個業務接口實現模式如下:

  • 創建session會話
  • 通過session獲得某個業務映射器(之前定義的映射器接口,實現是mybatis創建的,獲取到的並不是我們自己定義的DAO,這裏DAO實現了映射器相同接口,是爲了複用接口描述的行爲特徵,而實現是不同的兩套)
  • 調用映射器的業務方法
  • 關閉session
package com.mzj.mybatis.dao.annotation;

/**
 * @Auther: mazhongjia
 * @Date: 2020/6/12 12:52
 * @Version: 1.0
 */
import com.mzj.mybatis.entitles.Student;
import com.mzj.mybatis.utils.SqlSessionFactoryUtil4Annotation;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

public class StudentDao4Annotation implements StudentMapper {

    /**
     * 根據學生編號獲得學生對象
     */
    public Student selectStudentById(int id) {
        Student entity = null;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil4Annotation.openSession(true);

        //獲得一個映射器
        StudentMapper mapper=session.getMapper(StudentMapper.class);

        //查詢單個對象,指定參數爲3
        entity = mapper.selectStudentById(id);

        //關閉
        SqlSessionFactoryUtil4Annotation.closeSession(session);

        return entity;
    }


    /**
     * 根據學生姓名獲得學生集合
     */
    public List<Student> selectStudentsByName(String name) {
        List<Student> entities = null;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil4Annotation.openSession(true);

        //獲得一個映射器
        StudentMapper mapper=session.getMapper(StudentMapper.class);

        //查詢多個對象,指定參數
        entities =mapper.selectStudentsByName(name);
        //關閉
        SqlSessionFactoryUtil4Annotation.closeSession(session);
        return entities;
    }


    /**
     * 添加學生
     */
    public int insertStudent(Student entity) {
        //影響行數
        int rows=0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil4Annotation.openSession(true);

        //獲得一個映射器
        StudentMapper mapper=session.getMapper(StudentMapper.class);

        //執行添加
        rows = mapper.insertStudent(entity);
        //關閉
        SqlSessionFactoryUtil4Annotation.closeSession(session);
        return rows;
    }

    /**
     * 更新學生
     */
    public int updateStudent(Student entity) {
        //影響行數
        int rows=0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil4Annotation.openSession(true);

        //獲得一個映射器
        StudentMapper mapper=session.getMapper(StudentMapper.class);

        //執行更新
        rows =mapper.updateStudent(entity);
        //關閉
        SqlSessionFactoryUtil4Annotation.closeSession(session);
        return rows;
    }

    /**
     * 刪除學生
     */
    public int deleteStudent(int id) {
        //影響行數
        int rows=0;
        //打開一個會話
        SqlSession session = SqlSessionFactoryUtil4Annotation.openSession(true);

        //獲得一個映射器
        StudentMapper mapper=session.getMapper(StudentMapper.class);

        //執行刪除
        rows = mapper.deleteStudent(id);
        //關閉
        SqlSessionFactoryUtil4Annotation.closeSession(session);
        return rows;
    }

}

4、編寫測試用例

package com.mzj.mybatis.dao;

import com.mzj.mybatis.dao.annotation.StudentDao4Annotation;
import com.mzj.mybatis.dao.annotation.StudentMapper;
import com.mzj.mybatis.entitles.Student;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.List;

/**
 * StudentDao Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>09/26/2018</pre>
 */
public class StudentDao4AnnotationTest {

    StudentMapper dao;
    @Before
    public void before() throws Exception {
        dao=new StudentDao4Annotation();
    }

    @After
    public void after() throws Exception {
    }

    /**
     * Method: selectStudentById(int id)
     */
    @Test
    public void testSelectStudentById() throws Exception {
        Student entity=dao.selectStudentById(1);
        System.out.println(entity);
        Assert.assertNotNull(entity);
    }

    /**
     * Method: selectStudentsByName(String name)
     */
    @Test
    public void testSelectStudentsByName() throws Exception {
        List<Student> students=dao.selectStudentsByName("e");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

    /**
     * Method: insertStudent
     */
    @Test
    public void testInsertStudent() throws Exception {
        Student entity=new Student();
        entity.setName("張小強");
        entity.setSex("boy");

        Assert.assertEquals(1,dao.insertStudent(entity));
    }

    /**
     * Method: updateStudent
     */
    @Test
    public void testUpdateStudent() throws Exception {
        Student entity=dao.selectStudentById(7);
        entity.setName("張美麗");
        entity.setSex("girl");

        Assert.assertEquals(1,dao.updateStudent(entity));
    }

    /**
     * Method: deleteStudent
     */
    @Test
    public void testDeleteStudent() throws Exception {
        Assert.assertEquals(1,dao.deleteStudent(7));
    }
}

六、說明與注意事項

6.1、parameterType和resultType的區別

parameterType:在映射文件中通過parameterType指定輸入參數的類型。

resultType:在映射文件中通過resultType指定輸出結果的類型

6.2、#{}和${}的區別

#{}

#{}表示一個佔位符號,#{}接收輸入參數,類型可以是簡單類型,pojo、hashmap;

如果接收簡單類型,#{}中可以寫成value或其它名稱;

#{}接收pojo對象值,通過OGNL讀取對象中的屬性值,通過屬性.屬性.屬性...的方式獲取對象屬性值。使用#{}意味着使用的預編譯的語句,即在使用jdbc時的preparedStatement,sql語句中如果存在參數則會使用?作佔位符,我們知道這種方式可以防止sql注入,並且在使用#{}時形成的sql語句,已經帶有引號,例,select? * from table1 where id=#{id}? 在調用這個語句時我們可以通過後臺看到打印出的sql爲:select * from table1 where id='2' 加入傳的值爲2.也就是說在組成sql語句的時候把參數默認爲字符串。

${}

表示一個拼接符號,會引用sql注入,所以不建議使用${};

${}接收輸入參數,類型可以是簡單類型,pojo、hashmap;

如果接收簡單類型,${}中只能寫成value;

${}接收pojo對象值,通過OGNL讀取對象中的屬性值,通過屬性.屬性.屬性...的方式獲取對象屬性值。

使用${}時的sql不會當做字符串處理,是什麼就是什麼,如上邊的語句:select * from table1 where id=${id} 在調用這個語句時控制檯打印的爲:select * from table1 where id=2 ,假設傳的參數值爲2
從上邊的介紹可以看出這兩種方式的區別,我們最好是能用#{}則用它,因爲它可以防止sql注入,且是預編譯的,在需要原樣輸出時才使用${},如,
select * from ${tableName} order by ${id} 這裏需要傳入表名和按照哪個列進行排序 ,加入傳入table1、id 則語句爲:select * from table1 order by id
如果是使用#{} 則變成了select * from 'table1' order by 'id' 我們知道這樣就不對了。

 

Mybatis中#與$的區別

1.兩者都是動態的向sql語句中傳入需要的參數

2.#傳入的參數在SQL中顯示爲字符串

     eg:select id,name,age from student where id =#{id},當前端把id值1,傳入到後臺的時候,就相當於 select id,name,age from student where id ='1'.

3.$傳入的參數在SqL中直接顯示爲傳入的值

    eg:select id,name,age from student where id =${id},當前端把id值1,傳入到後臺的時候,就相當於 select id,name,age from student where id = 1.

4.#可以防止SQL注入的風險(語句的拼接)

5.但是如果使用在order by 中就需要使用 $.

我覺得#與的區別最大在於:#{} 傳入值時,sql解析時,參數是帶引號的,而的區別最大在於:#{} 傳入值時,sql解析時,參數是帶引號的,而{}穿入值,sql解析時,參數是不帶引號的。

一 : 理解mybatis中 $與#

    在mybatis中的$與#都是在sql中動態的傳入參數。

    eg:select id,name,age from student where name=#{name}  這個name是動態的,可變的。當你傳入什麼樣的值,就會根據你傳入的值執行sql語句。

二:使用$與#

   #{}: 解析爲一個 JDBC 預編譯語句(prepared statement)的參數標記符,一個 #{ } 被解析爲一個參數佔位符 。

   ${}: 僅僅爲一個純碎的 string 替換,在動態 SQL 解析階段將會進行變量替換。

    傳入一個不改變的字符串或者傳入數據庫字段(列名),例如要傳入order by 後邊的參數

     這種情況下必須使用${}。

綜上,#{}方式一般用於傳入字段值,並將該值作爲字符串加到執行sql中,一定程度防止sql注入;

           ${}方式一般用於傳入數據庫對象,例如傳入表名,不能防止sql注入,存在風險。

           模糊查詢中,如果使用如下方式:select * from reason_detail where reason_en like '%${reason}%',此處只能使用$,使用#的話,反而會被解析爲列,報錯java.sql.SQLException: Column 'reason' not found

情況一:只用  #{}

  <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select * from USER where userName=#{userName} and userPassword =#{userPassword};
  </select>

 結果:

 ==>  Preparing: select * from USER where userName=? and userPassword =?; 
 ==>  Parameters: mww(String), 123(String)
 <==  Total: 1

 結論:

    #{}會在預編譯期,生成兩個  ?,作爲佔位符。

 

情況二:一個用  ${} 一個用 #{}

  <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select * from USER where userName=${userName} and userPassword =#{userPassword};
  </select>

  結果:

 ==>  Preparing: select * from USER where userName=mww and userPassword =?; 
 ==>  Parameters: 123(String)

  結論:很顯然  ${}  是直接拼成字符串的 ,#{}  是生成  ?佔位符的。

     而且 因爲  userName:mww 是字符串,所以 這種寫法顯然也是錯誤的。

     會報出如下錯誤:

### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column 'mww' in 'where clause'

  所以正確的寫法是這樣的:爲字符串字段加上單引號 ' '

<select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select * from USER where userName='${userName}' and userPassword =#{userPassword};
</select>

  結果:

 ==>  Preparing: select * from USER where userName='mww' and userPassword =?; 
 ==>  Parameters: 123(String)
 <==  Total: 1

  結論:顯然這種寫法是正確的,從這裏可以看出,預編譯期   ${}  是直接把參數拼結到SQL中,

     運行時,就只傳入了一個 #{} 修飾的參數。

     ${}的這種寫法還有一個安全隱患,那就是 SQL注入。

 

情況三: ${}   SQL注入:

  <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select * from USER where userName='${userName}' and userPassword =#{userPassword};
  </select>

  結果:

 ==>  Preparing: select * from USER where userName='' OR 1=1 OR '' and userPassword =?; 
 ==>  Parameters: 65787682342367(String)
 <==  Total: 2

  結論:只要我們在  ${}  輸入   ' OR 1=1 OR '   無論後面的密碼輸入什麼都可以,查詢到數據,這種情況就是SQL注入。

 

 情況四:#{}  防止SQL注入

  <select id="getUserByNameAndPsw" parameterType="map" resultType="com.hotel3.model.User">
        select * from USER where userName=#{userName} and userPassword =#{userPassword};
  </select>

  結果:

 ==>  Preparing: select * from USER where userName=? and userPassword =?; 
 ==>  Parameters: ' OR 1=1 OR '(String), 123(String)
 <==  Total: 0

  結論:上面預編譯SQL的參數已經由佔位符 { ?} 代替,所以傳入的  ' OR 1=1 OR '  只會作爲  userName字段的值,

     而不會拼入執行的SQL。這樣就達到了防止SQL注入的目的。

 

在這種用法中, #{} 顯然比 ${} 用法更好。

那 ${} 爲什麼經常在  Mybatis  使用那?

 

${}正確用法(個人見解):

  1、同時傳入一個字段名和字段值:

User u=userService.getUserByNameAndPsw("userName,userType,userPassword",userName,userPassword);

    SQL: select ${arg0} from USER

  <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select ${arg0} from USER where userName=#{userName} and userPassword =#{userPassword};
  </select>

    結果:

 ==>  Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; 
 ==>  Parameters: mww(String), 123(String)
 <==  Total: 1

    結論:

      生成了我們想要SQL語句 :select userName,userType,userPassword from USER。。。。

 

   2、傳入兩個字段名和字段值:

User u=userService.getUserByNameAndPsw("userName,userType,userPassword",userName,userName,userPassword);

    SQL:  select ${arg0} from USER where ${arg1}=#{userName}

  <select id="getUserByNameAndPsw" resultType="com.hotel3.model.User">
        select ${arg0} from USER where ${arg1}=#{userName} and userPassword =#{userPassword};
  </select>

    結果:

 ==>  Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; 
 ==>  Parameters: mww(String), 123(String)
 <==  Total: 1

    結論:

      按照傳參的順序匹配 SQL 中的 ${arg0},${arg1}。。。

      生成我們想要的代碼,但這個方式會使Mybatis 的 Mapper 文件可讀性變差,

      如果不看其他的代碼,很難辨別  ${arg0} ,${arg1} 代表的含義。

   3、使用Map傳值,提高 Mapper 文件的可讀性

     Map map =new HashMap();
        map.put("selectValues","userName,userType,userPassword");
        map.put("userNamefieId","userName");
        map.put("userName",userName);
        map.put("userPassword",userPassword);
        User u=userService.getUserByNameAndPsw(map);

 

    Mapper 文件的 xml

  <select id="getUserByNameAndPsw" parameterType="map" resultType="com.hotel3.model.User">
        select ${selectValues} from USER where ${userNamefieId}=#{userName} and userPassword =#{userPassword};
  </select>

    結果:

 ==>  Preparing: select userName,userType,userPassword from USER where userName=? and userPassword =?; 
 ==>  Parameters: mww(String), 123(String)
 <==  Total: 1

    結論:

      結果和arg0、arg1的傳值方式相同,但 Mapper 文件的 xml 中的SQL語句可以

      通過對 map 中 key 值命名提高可讀性

 

  注:以上SQL,均爲預編期生成的SQL。

  注2:${} 每次傳值不同 SQL 語句不同,可以靈活生成 SQL, 

     但每次都需要進行預編譯,對效率會有影響,至於要不要使用 ${}還要具體情況具體分析。

6.3、selectOne()和selectList()的區別

selectOne表示查詢出一條記錄進行映射。如果使用selectOne可以實現使用selectList也可以實現(list中只有一個對象),如果查詢結果爲多條則會報錯。

selectList表示查詢出一個列表(多條記錄)進行映射,可以是0到n條記錄返回。

6.4、映射器選擇XML還是註解

以下是MyBatis官網對Mapper Annotations的解釋:

Mapper Annotations

Since the very beginning, MyBatis has been an XML driven framework. The configuration is XML based, and the Mapped Statements are defined in XML. With MyBatis 3, there are new options available. MyBatis 3 builds on top of a comprehensive and powerful Java based Configuration API. This Configuration API is the foundation for the XML based MyBatis configuration, as well as the new Annotation based configuration. Annotations offer a simple way to implement simple mapped statements without introducing a lot of overhead.

NOTE : Java Annotations are unfortunately limited in their expressiveness and flexibility. Despite a lot of time spent in investigation, design and trials, the most powerful MyBatis mappings simply cannot be built with Annotations – without getting ridiculous that is. C# Attributes (for example) do not suffer from these limitations, and thus MyBatis.NET will enjoy a much richer alternative to XML. That said, the Java Annotation based configuration is not without its benefits.

翻譯:

(最初MyBatis是基於XML驅動的框架。MyBatis的配置是基於XML的,語句映射也是用XML定義的。對於MyBatis3,有了新的可選方案。MyBatis3 是建立在全面且強大的Java配置API之上的。 該配置API是MyBatis基於XML配置的基礎,也是基於註解配置的基礎。註解提供了簡單的方式去實現簡單的映射語句,不需要花費大量的開銷。

注意:很不幸的是,java註解在表現和靈活性上存在限制。雖然在調研、設計和測試上花費了很多時間,但是最強大的MyBatis映射功能卻無法用註解實現。這沒有什麼可笑的。舉例來說,C#的特性就沒有這個限制,所以MyBatis.NET 能擁有一個功能豐富的多的XML替代方案。所以,Java基於註解的配置是依賴於其自身特性的。)

長遠來看建議選擇XML作爲映射器

http://www.mybatis.org/mybatis-3/java-api.html

七、視頻

https://www.bilibili.com/video/av32447485/

八、示例

https://git.coding.net/zhangguo5/MyBatis02.git

九、作業

1、請使用MyBatis完成一個用戶管理的數據訪問功能,要求實現根據用戶名查詢用戶對象(id,username,password,name,email,state)功能,表中至少5個字段。

2、請分別使用XML與註解兩種方式實現對象用戶表(Users)的單條記錄查詢、多條記錄查詢、增加、修改與刪除功能,要求單元測試通過。

3、添加用戶成功後返回用戶的編號,而不是影響行數。(選作)

4、實現多個條件組合查詢,類似在電商平臺購物可以選擇0-n個條件,且可以自由組合。(選作)

5、實現分頁功能。(選作)

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