2. Mybatis映射文件詳解

1 mybatis映射文件

1.1 頂級元素標籤

標籤 作用
cache 該命名空間的緩存配置
cache-ref 引用其它命名空間的緩存配置
resultMap 描述數據庫結果集與java對象的映射關係,是最複雜也是最強大的元素
parameterMap 老式風格的參數映射,此元素已被廢棄
sql 可被其它語句引用的可重用語句塊
insert dao層方法與數據庫中插入語句的映射關係
update dao層方法與數據庫中更新語句的映射關係
delete dao層方法與數據庫中刪除語句的映射關係
select dao層方法與數據庫中查詢語句的映射關

1.2 insert、update、delete標籤內屬性

屬性 描述
id 用來標識跟dao接口中匹配的方法,必須與方法的名字一一對應上
parameterType 可以傳入這條語句的形參列表中參數的全限定類名或別名。該屬性可選,因爲 MyBatis 可以通過類型處理器(TypeHandler)推斷出具體傳入語句的參數,默認值爲未設置(unset)
parameterMap 已廢棄
flushCache 將其設置爲 true 後,只要語句被調用,都會導致本地緩存和二級緩存被清空,默認值:select標籤false
useCache 將其設置爲 true 後,將會導致本條語句的結果被二級緩存緩存起來,默認值:select標籤 true
timeout 超時後拋出異常
statementType 用來選擇執行sql語句的方式,statement:最基本的jdbc操作,用來表示一個sql語句,不能防止sql注入,prepared:preparestatment:採用預編譯方式,防止sql注入,callable:調用存儲過程。默認值:PREPARED
useGeneratedKeys 設置爲true時,當insert或update後,可以獲取到表中自動遞增的主鍵值,默認值:false
keyProperty 指定useGeneratedKey所獲取到的主鍵要賦值到哪個屬性中。如果生成列不止一個,可以用逗號分隔多個屬性名稱。 不設置該值時,如果未給User中id賦值,且數據庫中設計中,對主鍵id設置了自動遞增,插入後,雖然數據庫中id字段會有值,但java程序中的User對象的id屬性沒有值
keyColumn 僅適用於 insert 和 update,設置生成鍵值在表中的列名,在某些數據庫(像 PostgreSQL)中,當主鍵列不是表中的第一列的時候,是必須設置的。如果生成列不止一個,可以用逗號分隔多個屬性名稱
databaseId 如果配置了數據庫廠商標識(databaseIdProvider),MyBatis 會加載所有不帶 databaseId 或匹配當前 databaseId 的語句;如果帶和不帶的語句都有,則不帶的會被忽略
<!--數據庫支持自增的寫法-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into user(user_name) values(#{userName})
</insert>
<!--數據庫不支持自增的寫法-->
<insert id="insertUser2" >
    <selectKey order="BEFORE" keyProperty="id" resultType="integer">
        select max(id)+1 from user
    </selectKey>
    insert into user(id,user_name) values(#{id},#{userName})
</insert>

1.3 sql標籤

<!--多次用到重複sql段時,可以將sql的某段單獨拿出來-->
<sql id="sometable">
  ${prefix}Table
</sql>

<sql id="someinclude">
  from
    <include refid="${include_target}"/>
</sql>

<select id="select" resultType="map">
  select
    field1, field2, field3
  <include refid="someinclude">
    <property name="prefix" value="Some"/>
    <property name="include_target" value="sometable"/>
  </include>
</select>

1.4 select標籤

1.4.1 相關屬性
屬性 描述
resultType 表示返回的結果類型,此類型只能返回單一的對象,使用較少。當返回的結果是一個集合時,並不需要resultMap,只需要resultType指定集合中的元素類型即可
resultMap 當進行關聯查詢時,返回的結果的對象中,還包含另一個對象的引用時,又或者表中字段名稱和屬性名不符時,都需要使用resultMap來自定義結果集合

2 參數傳遞

2.1 Dao層方法中只有一個參數

  1. 基本類型:使用#{隨便寫}
  2. 引用類型:使用#{類的屬性名稱}

2.2 Dao層方法中多個參數

  1. 方案一:通過#{arg0},#{arg1},或者#{param1},#{param2}等方式來獲取值
    1. 因爲mybatis在傳入多個參數的時候,會將這些參數封裝到一個map中,此時map中的key就是arg0、arg1、param1、param2這些值,但此時無法根據參數的名稱來獲取具體的值
  2. 方案二:可以在Dao層的方法上加入@Param註釋,指定方法中參數與sql中綁定參數對照關係
//java代碼
Emp selectEmpByNoAndName(@Param("empno") Integer empno, @Param("ename") String ename);
//xml中配置
<select id="selectEmpByNoAndName" resultType="com.mashibing.bean.Emp">
    select * from emp where empno=#{empno} and ename=#{ename}
</select>

2.3 Dao層方法中使用Map類型的參數

  1. 其實就是以xml中#{key}中的key作爲map的key,這樣就能將map的value,傳遞給指定佔位符
//java代碼
Emp selectEmpByNoAndName2(Map<String,Object> map);
//xml中配置
<select id="selectEmpByNoAndName2" resultType="com.mashibing.bean.Emp">
    select * from emp where empno=#{empno} and ename=#{ename}
</select>

3 參數的取值方式

  1. 在xml文件中編寫sql語句的時候有兩種取值的方式,分別是#{}和${}
<select id="selectEmpByNoAndName" resultType="com.mashibing.bean.Emp">
    select * from #{t} where empno=${empno} and ename=${ename}
</select>
  1. 使用#{}方式進行取值:採用的是參數預編譯的方式,參數的位置使用"?"進行替代,不會出現sql注入的問題
--打印語句信息如下
select * from emp where empno=? and ename=?
  1. 使用sql使{}方式進行取值:採用的是直接跟sql語句進行拼接的方式,當需要動態傳入表名、列名時需要使用{}
--打印語句信息如下
select * from emp where empno=7369 and ename='SMITH'

4 返回集合類型

  1. 當返回值是集合類型的時候,resultType寫的是集合中元素的類型
List<Emp> selectAllEmp();
<select id="selectAllEmp" resultType="com.mashibing.bean.Emp">
    select  * from emp
</select>
  1. 如果集合中的元素,沒有具體的實體類對應,可以指定元素爲map型,map的key爲表的列名,map的value爲具體值
public List<Map<String,Integer>> getMapList();
<select id="getMapList" resultType="map">
    select t.ename,b.dname from emp t,dept b where t.deptno=b.deptno
</select>
  1. 返回值也可以是Map類型,也可以表示結果集合
//必須標識Emp中哪個屬性作爲key
@MapKey("empno")
Map<Integer,Emp> getAllEmpReturnMap();
<select id="getAllEmpReturnMap" resultType="com.mashibing.bean.Emp">
    select * from emp
</select>

5 自定義結果集

  1. Dog.java
package com.mashibing.bean;

public class Dog {
    private Integer id;
    private String name;
    private Integer age;
    private String gender;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getGender() {
        return gender;
    }

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

    @Override
    public String toString() {
        return "Dog{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                '}';
    }
}
  1. dog.sql
/*
Navicat MySQL Data Transfer

Source Server         : node01
Source Server Version : 50729
Source Host           : 192.168.85.111:3306
Source Database       : demo

Target Server Type    : MYSQL
Target Server Version : 50729
File Encoding         : 65001

Date: 2020-03-24 23:54:22
*/

SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `dog`
-- ----------------------------
DROP TABLE IF EXISTS `dog`;
CREATE TABLE `dog` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `dname` varchar(255) DEFAULT NULL,
  `dage` int(11) DEFAULT NULL,
  `dgender` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of dog
-- ----------------------------
INSERT INTO dog VALUES ('1', '大黃', '1', '雄');
INSERT INTO dog VALUES ('2', '二黃', '2', '雌');
INSERT INTO dog VALUES ('3', '三黃', '3', '雄');
  1. DogDao.java
package com.mashibing.dao;

import com.mashibing.bean.Dog;

public interface DogDao {
    public Dog selectDogById(Integer id);
}
  1. DogDao.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.mashibing.dao.DogDao">
    <!--
        1. 在使用mybatis進行查詢的時候,mybatis默認會幫我們進行結果的封裝,但是要求列名跟屬性名稱必須一一對應,如果無法對應,可以使用以下兩種方案解決
               1. 在編寫sql語句時,爲表中列添加別名
               2. 自定義封裝結果集
    -->

    <!--1. 自定義封裝結果集:根據查詢的數據進行結果的封裝要使用resultMap屬性,表示使用自定義規則-->
    <select id="selectDogById" resultMap="myDog">
      select * from dog where id = #{id}
   </select>

    <!--
        1. 自定義結果集,將每一個列的數據跟javaBean的對象屬性對應起來
            1. type:表示爲哪一個javaBean對象進行對應
            2. id:唯一標識,方便其他屬性標籤進行引用
    -->
    <resultMap id="myDog" type="com.mashibing.bean.Dog">
        <!--
            1. id:表示指定主鍵列的對應規則
            2. column:表示列名
            3. property:指定javaBean的屬性
        -->
        <id column="id" property="id"></id>
        <!--result表示設置其他列的對應關係-->
        <result column="dname" property="name"></result>
        <result column="dage" property="age"></result>
        <result column="dgender" property="gender"></result>
    </resultMap>
    <!--2. 爲列定義別名-->
<!--    <select id="selectDogById" resultType="com.mashibing.bean.Dog">-->
<!--         select id id,dname name,dage age,dgender gender from dog where id = #{id}-->
<!--    </select>-->

    <!--3. 錯誤寫法:這種方式是查詢不到任何結果的,因爲屬性名跟列名並不是一一對應的-->
<!--    <select id="selectDogById" resultType="com.mashibing.bean.Dog">-->
<!--        select * from dog where id = #{id}-->
<!--    </select>-->
</mapper>

6 聯合查詢

6.1 多對一

  1. Emp.java
package com.mashibing.bean;

import java.util.Date;

public class Emp {

    private Integer empno;
    private String ename;
    private String job;
    private Integer mgr;
    private Date hiredate;
    private Double sal;
    private Double common;
    private Dept dept;

    public Emp() {
    }

    public Emp(Integer empno, String ename) {
        this.empno = empno;
        this.ename = ename;
    }

    public Emp(Integer empno, String ename, String job, Integer mgr, Date hiredate, Double sal, Double common, Dept dept) {
        this.empno = empno;
        this.ename = ename;
        this.job = job;
        this.mgr = mgr;
        this.hiredate = hiredate;
        this.sal = sal;
        this.common = common;
        this.dept = dept;
    }

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getEname() {
        return ename;
    }

    public void setEname(String ename) {
        this.ename = ename;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public Integer getMgr() {
        return mgr;
    }

    public void setMgr(Integer mgr) {
        this.mgr = mgr;
    }

    public Date getHiredate() {
        return hiredate;
    }

    public void setHiredate(Date hiredate) {
        this.hiredate = hiredate;
    }

    public Double getSal() {
        return sal;
    }

    public void setSal(Double sal) {
        this.sal = sal;
    }

    public Double getCommon() {
        return common;
    }

    public void setCommon(Double common) {
        this.common = common;
    }

    public Dept getDept() {
        return dept;
    }

    public void setDept(Dept dept) {
        this.dept = dept;
    }

    @Override
    public String toString() {
        return "Emp{" +
                "empno=" + empno +
                ", ename='" + ename + '\'' +
                ", job='" + job + '\'' +
                ", mgr=" + mgr +
                ", hiredate=" + hiredate +
                ", sal=" + sal +
                ", common=" + common +
                ", dept=" + dept +
                '}';
    }
}

  1. Dept.java
package com.mashibing.bean;

public class Dept {
    private Integer deptno;
    private String dname;
    private String loc;

    public Dept() {
    }

    public Dept(Integer deptno, String dname, String loc) {
        this.deptno = deptno;
        this.dname = dname;
        this.loc = loc;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                '}';
    }
}

  1. EmpDao.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.mashibing.dao.EmpDao">

    <select id="selectEmpAndDept" resultMap="empDept">
        select * from emp left join dept on emp.deptno = dept.deptno where empno = #{empno};
    </select>
    <!--方案一:如果有實體類對象,就寫成對象.屬性的方式-->
<!--    <resultMap id="empDept" type="com.mashibing.bean.Emp">-->
<!--        <id column="empno" property="empno"></id>-->
<!--        <result column="ename" property="ename"></result>-->
<!--        <result column="job" property="job"></result>-->
<!--        <result column="mgr" property="mgr"></result>-->
<!--        <result column="hiredate" property="hiredate"></result>-->
<!--        <result column="sal" property="sal"></result>-->
<!--        <result column="comm" property="common"></result>-->
<!--        <result column="deptno" property="dept.deptno"></result>-->
<!--        <result column="dname" property="dept.dname"></result>-->
<!--        <result column="loc" property="dept.loc"></result>-->
<!--    </resultMap>-->
    <!--方案二:使用association標籤,property表示實體類中屬性名-->

-->
    <resultMap id="empDept" type="com.mashibing.bean.Emp">
        <id column="empno" property="empno"></id>
        <result column="ename" property="ename"></result>
        <result column="job" property="job"></result>
        <result column="mgr" property="mgr"></result>
        <result column="hiredate" property="hiredate"></result>
        <result column="sal" property="sal"></result>
        <result column="comm" property="common"></result>
        <association property="dept" javaType="com.mashibing.bean.Dept">
            <id column="deptno" property="deptno"></id>
            <result column="dname" property="dname"></result>
            <result column="loc" property="loc"></result>
        </association>
    </resultMap>

</mapper>
  1. EmpDao.java
public Emp selectEmpAndDept(Integer empno);
  1. Test
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectEmpAndDept(7369);
System.out.println(emp);

6.2 一對多

  1. Dept.java
package com.mashibing.bean;

import java.util.List;

public class Dept {
    private Integer deptno;
    private String dname;
    private String loc;
    private List<Emp> emps;

    public Dept() {
    }

    public Dept(Integer deptno, String dname, String loc) {
        this.deptno = deptno;
        this.dname = dname;
        this.loc = loc;
    }

    public Integer getDeptno() {
        return deptno;
    }

    public void setDeptno(Integer deptno) {
        this.deptno = deptno;
    }

    public String getDname() {
        return dname;
    }

    public void setDname(String dname) {
        this.dname = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

    public List<Emp> getEmps() {
        return emps;
    }

    public void setEmps(List<Emp> emps) {
        this.emps = emps;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "deptno=" + deptno +
                ", dname='" + dname + '\'' +
                ", loc='" + loc + '\'' +
                ", emps=" + emps +
                '}';
    }
}
  1. DeptDao.java
public Dept getDeptAndEmps(Integer deptno);
  1. DeptDao.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.mashibing.dao.DeptDao">
    <select id="getDeptAndEmps" resultMap="deptEmp">
        select * from dept left join emp on dept.deptno = emp.deptno where dept.deptno=#{deptno}
    </select>
    <!--1. 仍然是使用resultMap定製結果-->
    <resultMap id="deptEmp" type="com.mashibing.bean.Dept">
        <id property="deptno" column="deptno"></id>
        <result property="dname" column="dname"></result>
        <result property="loc" column="loc"></result>
        <!--2. 使用collection標籤定製集合類型的屬性的值
                1. property:仍然是指定屬性名
                2. ofType:指定集合中的元素類型
        -->
        <collection property="emps" ofType="com.mashibing.bean.Emp">
            <id property="empno" column="empno"></id>
            <result column="ename" property="ename"></result>
            <result column="job" property="job"></result>
            <result column="mgr" property="mgr"></result>
            <result column="hiredate" property="hiredate"></result>
            <result column="sal" property="sal"></result>
            <result column="comm" property="common"></result>
            <!--3. 注意此處不能再寫association,否則就循環了-->
        </collection>
    </resultMap>
</mapper>
  1. Test
DeptDao mapper = sqlSession.getMapper(DeptDao.class);
Dept emp = mapper.getDeptAndEmps(20);
System.out.println(emp.getEmps().get(1).getEname());

7 分步查詢

7.1 多對一

  1. DeptDao.java
public Dept getDeptAndEmpsBySimple(Integer deptno);
  1. EmpDao.java
Emp selectEmpAndDeptBySimple(Integer empno);
  1. DeptDao.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.mashibing.dao.DeptDao">
    <select id="getDeptAndEmpsBySimple" resultType="com.mashibing.bean.Dept">
        select * from dept where deptno = #{deptno}
    </select>
</mapper>
  1. EmpDao.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.mashibing.dao.EmpDao">

    <select id="selectEmpAndDeptBySimple" resultMap="simpleEmpAndDept">
        select * from emp where empno = #{empno}
    </select>
    <resultMap id="simpleEmpAndDept" type="com.mashibing.bean.Emp">
        <id column="empno" property="empno"></id>
        <result column="ename" property="ename"></result>
        <result column="job" property="job"></result>
        <result column="mgr" property="mgr"></result>
        <result column="hiredate" property="hiredate"></result>
        <result column="sal" property="sal"></result>
        <result column="comm" property="common"></result>
        <!--1. 表示使用select語句的結果爲dept這個屬性賦值,而列deptno作爲select語句的傳入參數
            2. 此處不需要再次定義getDeptAndEmpsBySimple查詢的dept表中列名和Dept實體類中屬性的對應關係,因爲getDeptAndEmpsBySimple中已經定義過了
            3. 當執行selectEmpAndDeptBySimple方法時,mybatis會先執行select * from emp where empno = ?,然後再根據查出的deptno列的值,執行select * from dept where deptno = ? -->
        <association property="dept" select="com.mashibing.dao.DeptDao.getDeptAndEmpsBySimple" column="deptno">
        </association>
    </resultMap>
</mapper>
  1. Test
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = mapper.selectEmpAndDeptBySimple(7369);
System.out.println(emp);

7.2 一對多

  1. EmpDao.java
Emp selectEmpByStep(Integer empno);
  1. DeptDao.java
public Dept getDeptAndEmpsByStep(Integer deptno);
  1. EmpDao.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.mashibing.dao.EmpDao">
    <select id="selectEmpByStep" resultType="com.mashibing.bean.Emp">
        select * from emp where deptno = #{deptno}
    </select>
</mapper>
  1. DeptDao.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.mashibing.dao.DeptDao">
    <select id="getDeptAndEmpsByStep" resultMap="deptEmpByStep">
        select * from dept where deptno = #{deptno}
    </select>
    <resultMap id="deptEmpByStep" type="com.mashibing.bean.Dept">
        <id property="deptno" column="deptno"></id>
        <result property="dname" column="dname"></result>
        <result property="loc" column="loc"></result>
        <collection property="emps" select="com.mashibing.dao.EmpDao.selectEmpByStep" column="deptno">
        </collection>
    </resultMap>
</mapper>
  1. Test
DeptDao mapper = sqlSession.getMapper(DeptDao.class);
Dept deptAndEmpsByStep = mapper.getDeptAndEmpsByStep(10);
System.out.println(deptAndEmpsByStep);

8 延遲查詢

  1. 當使用分步查詢來關聯兩個表進行查詢時,在真正需要使用關聯表中的值時,才發起語句。例如在java程序中dept.getDname時,不會發起其關聯的語句,當dept.getEmps時,纔會真正發起sql語句
  2. mybatis-config.xml
<settings>
    <!--1. 需要在全局配置中開啓延時加載-->
 	<!--2. name爲aggresiveLazyLoading的屬性在3.4.1之前,默認值爲true,而aggresiveLazyLoading爲true時,即使方法中fetchType設置延遲加載也是不起作用的,所以想開啓延遲加載,在3.4.1版本前,aggresiveLazyLoading需要設爲false,3.4.1之後默認是false,所以不需要特殊設置-->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  1. 如果在全局配置中設置了延遲加載,但是希望在某一個sql語句查詢的時候不使用延遲策略,可以添加fetchType屬性
<association property="dept" select="com.mashibing.dao.DeptDao.getDeptAndEmpsBySimple" column="deptno" fetchType="eager"/>
  1. Test
DeptDao mapper = sqlSession.getMapper(DeptDao.class);
//1. 此時發起第一個查詢語句
Dept deptAndEmpsByStep = mapper.getDeptAndEmpsByStep(10);
//2. 該語句不會真正發起第二次查詢語句
System.out.println(deptAndEmpsByStep.getDname());
//3. 此時真正發起第二次查詢語句
System.out.println(deptAndEmpsByStep.getEmps());

9 動態sql

  1. 動態sql是MyBatis的強大特性之一,此處不是指plsql中的動態sql,此處的動態sql用於替代JDBC中拼接sql語句的方式,可以方便地拼接sql,因此此處的動態sql是不會導致大量硬解析的

9.1 if

  1. EmpDao.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.mashibing.dao.EmpDao">
    <select id="getEmpByCondition" resultType="com.mashibing.bean.Emp">
        select * from emp where
        <if test="empno!=null">
            empno > #{empno} and
        </if>
        <if test="ename!=null">
            ename like #{ename} and
        </if>
        <!--此時如果sal爲null,前面的and還會保留,導致整個語句執行失敗-->
        <if test="sal!=null">
            sal > #{sal}
        </if>
    </select>
</mapper>
  1. EmpDao.java
public List<Emp> getEmpByCondition(Emp emp);
  1. Test.java
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp emp = new Emp();
emp.setEmpno(6500);
emp.setEname("%E%");
//不設置sal,語句無法正常執行
emp.setSal(500.0);
List<Emp> empByCondition = mapper.getEmpByCondition(emp);
for (Emp emp1 : empByCondition) {
    System.out.println(emp1);
}

9.2 where

  1. 如果我們傳入的參數值有缺失,此時候拼接的sql語句就會變得有問題,例如不傳參數或者丟失最後一個參數,那麼語句中就會多一個where或者and的關鍵字
  2. Mybatis中給出瞭解決方案,使用where元素,此時只會在子元素返回任何內容的情況下才插入where子句。而且,若子句的開頭爲and或or,where元素也會將它們去除
  3. EmpDao.xml
<select id="getEmpByCondition" resultType="com.mashibing.bean.Emp">
    select * from emp
    <where>
        <if test="empno!=null">
            empno > #{empno}
        </if>
        <if test="ename!=null">
            <!--1. and或or必須寫在前面,因爲子句開頭的and才能被去除
                2. 傳統寫法中,where 1=1這種寫法是爲了方便後面拼串時,統一前面加and即可,但無法處理or的情況-->
            and ename like #{ename}
        </if>
        <if test="sal!=null">
            and sal > #{sal}
        </if>
    </where>
</select>

9.3 trim

  1. 可以使用trim定製子句
<!--
    1. trim截取字符串:可以自定義where格式
    2. prefix:前綴,爲sql整體添加一個前綴
    3. prefixOverrides:去除整體字符串前面多餘的字符
    4. suffixOverrides:去除後面多餘的字符串
-->
<select id="getEmpByCondition" resultType="com.mashibing.bean.Emp">
    select * from emp
    <!--1. 如果想去除多個前綴,可以寫爲prefixOverrides="and|or"-->
    <trim prefix="where" prefixOverrides="and" suffixOverrides="and">
        <if test="empno!=null">
            empno > #{empno} and
        </if>
        <if test="ename!=null">
            ename like #{ename} and
        </if>
        <if test="sal!=null">
            sal > #{sal} and
        </if>
    </trim>
</select>

9.4 foreach

  1. 一般用於構建in條件語句
  2. EmpDao.xml
<!--
1. foreach是對集合進行遍歷
    1. collection:指定要遍歷的集合
    2. close:表示以什麼結束
    3. index:給定一個索引值,也就是循環到的下標,也就是list.get(i)中這個i
    4. item:遍歷的每一個元素的值
    5. open:表示以什麼開始
    6. separator:表示多個元素的分隔符
-->
<select id="getEmpByDeptnos" resultType="com.mashibing.bean.Emp">
    select * from emp where deptno in
    <foreach collection="deptnos" close=")" index="idx" item="deptno" open="(" separator=",">
        #{deptno}
    </foreach>
</select>
  1. EmpDao
//此處必須使用@Param,否則MyBatis無法自動將參數列表中deptnos和xml中deptnos關聯上
public List<Emp> getEmpByDeptnos(@Param("deptnos") List deptnos);

9.5 choose

  1. 之前的if中沒法使用else if,choose功能就類似else if,也類似java中的switch語句
  2. EmpDao.xml
<select id="getEmpByConditionChoose" resultType="com.mashibing.bean.Emp">
    select * from emp
    <where>
        <choose>
            <!--1. 滿足第一個後,後面的都不會再執行,相當於if elseif-->
            <when test="empno!=null">
                empno > #{empno}
            </when>
            <when test="ename!=null">
                ename like #{ename}
            </when>
            <when test="sal!=null">
                sal > #{sal}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </where>
</select>

9.6 set

  1. set元素可以忽略不需要更新的列,類似之前where的功能
  2. EmpDao.xml
<update id="updateEmpByEmpno">
    update emp
    <set>
        <if test="empno!=null">
            empno=#{empno},
        </if>
        <if test="ename!=null">
            ename = #{ename},
        </if>
        <if test="sal!=null">
            sal = #{sal}
        </if>
    </set>
    <where>
        empno = #{empno}
    </where>
</update>

10 緩存

  1. 如果沒有緩存,那麼每次查詢的時候,都需要從數據庫加載數據,會造成io問題,所以很多情況下,如果連續執行兩條相同的sql語句,可以直接從緩存中讀取,如果獲取不到,再去數據庫中查詢,這意味着查詢完成的結果,需要放入緩存中
  2. mybatis緩存分類
    1. 一級緩存:默認開啓,只在當前sqlSession(會話)中緩存,每次查詢後,會將數據存儲在sqlSession中,每次查詢前,先嚐試在sqlSession中查詢是否已經存在該結果,如果存在,直接從緩存中獲取結果。sqlSession關閉後自動失效
    2. 二級緩存:需要手動開啓,全局範圍內緩存,sqlSession關閉後,纔會生效
    3. 第三方緩存:集成第三方組件充當緩存作用

10.1 一級緩存的使用

  1. 下面的案例中,發送了兩個相同的請求,但是sql語句僅僅執行了一次,因此意味着第一次查詢的時候已經將結果進行了緩存
  2. Test
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> list = mapper.selectAllEmp();
for (Emp emp : list) {
    System.out.println(emp);
}
System.out.println("--------------------------------");
List<Emp> list2 = mapper.selectAllEmp();
for (Emp emp : list2) {
    System.out.println(emp);
}

10.2 一級緩存失效

10.2.1 開啓了多個sqlsession
  1. Test
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List<Emp> list = mapper.selectAllEmp();
for (Emp emp : list) {
    System.out.println(emp);
}
System.out.println("================================");
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
//由於和上面的語句使用的不是一個sqlSession,因此會再發送一次語句,因此可以證明一級緩存失效
List<Emp> list2 = mapper2.selectAllEmp();
for (Emp emp : list2) {
    System.out.println(emp);
}
sqlSession.close();
sqlSession2.close();
10.2.2 爲sql語句傳遞的參數不一致
  1. 如果參數爲對象,即使二者是同一個對象,但他們屬性值不同,也不會走緩存
10.2.3 兩次查詢間產生了update、insert語句
  1. 即使這個update語句和之前的select語句一點關係都沒有,再次進行查詢也無法使用緩存
  2. 但如果是直接數據庫或其它連接修改數據,那麼第二次查詢仍然走緩存,因爲sqlSession並不知道第二個sqlSession的存在
  3. EmpDao.java
Emp findEmpByEmpno(Integer empno);
int updateEmp(Integer empno);
  1. EmpDao.xml
<select id="findEmpByEmpno" resultType="com.mashibing.bean.Emp">
	select  * from emp where empno=#{empno}
</select>
<update id="updateEmp" >
	update emp set ename='handidiao' where empno=#{empno}
</update>
  1. Test
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp empByEmpno = mapper.findEmpByEmpno(7369);
System.out.println(empByEmpno);
System.out.println("================================");
//修改的內容和查詢的內容根本沒關係,仍然導致後面查詢無法使用緩存
int i = mapper.updateEmp(1111);
System.out.println(i);
System.out.println("================================");
Emp empByEmpno1 = mapper.findEmpByEmpno(7369);
System.out.println(empByEmpno1);
sqlSession.close();
10.2.4 兩次查詢期間,手動清空了緩存
  1. Test
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
Emp empByEmpno = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno);
System.out.println("================================");
System.out.println("手動清空緩存");
sqlSession.clearCache();
System.out.println("================================");
Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno1);
sqlSession.close();

10.3 二級緩存

10.3.1 開啓二級緩存
  1. 全局配置文件中添加如下配置
<setting name="cacheEnabled" value="true"/>
  1. 需要在使用二級緩存的映射文件(EmpDao.xml)中,使用標籤標註
<!--EmpDao.xml中/mapper前最後一行添加-->
<cache/>
  1. 語句中涉及到的所有實體類必須要實現Serializable接口
  2. Test
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
Emp empByEmpno = mapper.findEmpByEmpno(1111);
System.out.println(empByEmpno);
//1. 如果是兩個會話, 第一個關閉後,數據才能進入二級緩存
//2. 而當查詢時,默認先從二級緩存中查詢是否有數據,如果從二級緩存查到了,就不會再將該數據放入一級緩存
sqlSession.close();
//3. 下面語句執行後,打印:DEBUG [main] - Cache Hit Ratio [com.mashibing.dao.EmpDao]: 0.5
//4. 0.5表示二級緩存命中率,第一個語句執行時,先從二級緩存中查找,未查找到,因此開始該值爲0,本次由於數據已經進入二級緩存,因此從二級緩存中能查到
//5. 因此,相當於使用了兩次二級緩存,命中一次,命中率爲0.5,同時也能證明下方語句確實使用了二級緩存
//6. 證明先從二級緩存中查找數據:發現查詢在一級緩存中存在的語句時,二級緩存命中率降低,因此證明一定不是先查詢一級緩存,如果先查詢一級緩存,那麼就能查到,也就無法訪問二級緩存,因此二級緩存命中率不應該降低
Emp empByEmpno1 = mapper2.findEmpByEmpno(1111);
System.out.println(empByEmpno1);
sqlSession2.close();
10.3.2 cache標籤中的屬性
  1. eviction:表示緩存淘汰機制,默認是LRU
    1. LRU:最近最少使用的,移除最長時間不被使用的對象
    2. FIFO:先進先出,按照對象進入緩存的順序來移除
    3. SOFT:軟引用,移除基於垃圾回收器狀態和軟引用規則的對象
    4. WEAK:弱引用,更積極地移除基於垃圾收集器狀態和弱引用規則的對象
  2. flushInternal:設置多長時間進行緩存刷新,單位毫秒。默認情況沒有刷新間隔,僅調用語句時刷新緩存
  3. size:引用條數,正整數,表示緩存中可以存儲多少個對象,一般不設置,設置的話不要太大,會導致內存溢出
  4. readonly
    1. true:只讀緩存,會給所有調用這返回緩存對象的相同實例,因此不安全,修改了一個其他也被修改
    2. false:讀寫緩存,會返回緩存對象的拷貝(序列化實現),這種方式比較安全,默認爲false
10.3.3 二級緩存的作用範圍
  1. 如果設置了全局的二級緩存配置,可以在select標籤中,通過useCache屬性,設置不使用二級緩存
  2. 增刪改操作默認會清空一級緩存和二級緩存,而查詢操作不會,這是通過flushCache屬性設置的,增刪改操作默認值爲true,而查詢操作默認是false
  3. 可以使用sqlSession.clearCache()手動清除一級緩存

10.4 整合第三方緩存

  1. 在某些情況下我們也可以自定義實現緩存,或爲其他第三方緩存方案創建適配器,來完全覆蓋緩存行爲
  2. Mybatis推薦使用ehcache作爲第三方緩存。在github中有使用第三方緩存的相關配置介紹
  3. 導入對應的maven依賴
<!-- https://mvnrepository.com/artifact/org.ehcache/ehcache -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.0-alpha1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>2.0.0-alpha1</version>
    <scope>test</scope>
</dependency>
  1. 導入ehcache配置文件,名必須爲echcache.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <!--屬性說明:
        1. diskStore:指定數據在磁盤中的存儲位置
        2. defaultCache:當藉助CacheManager.add("demoCache")創建Cache時,EhCache便會採用<defalutCache/>指定的的管理策略
            1. 以下屬性是必須的
                1. maxElementsInMemory - 在內存中緩存的element的最大數目
                2. maxElementsOnDisk - 在磁盤上緩存的element的最大數目,若是0表示無窮大
                3. eternal - 設定緩存的elements是否永遠不過期。如果爲true,則緩存的數據始終有效,如果爲false那麼還要根據timeToIdleSeconds,timeToLiveSeconds判斷
                4. overflowToDisk - 設定當內存緩存溢出的時候是否將過期的element緩存到磁盤上
            2. 以下屬性是可選的
                1. timeToIdleSeconds - 當緩存在EhCache中的數據前後兩次訪問的時間超過timeToIdleSeconds的屬性取值時,這些數據便會刪除,默認值是0,也就是可閒置時間無窮大
                2. timeToLiveSeconds - 緩存element的有效生命期,默認是0.,也就是element存活時間無窮大
                3. diskSpoolBufferSizeMB 這個參數設置DiskStore(磁盤緩存)的緩存區大小.默認是30MB.每個Cache都應該有自己的一個緩衝區.
                4. diskPersistent - 在VM重啓的時候是否啓用磁盤保存EhCache中的數據,默認是false。
                5. diskExpiryThreadIntervalSeconds - 磁盤緩存的清理線程運行間隔,默認是120秒。每個120s,相應的線程會進行一次EhCache中數據的清理工作
                6. memoryStoreEvictionPolicy - 當內存緩存達到最大,有新的element加入的時候, 移除緩存中element的策略。默認是LRU(最近最少使用),可選的有LFU(最不常使用)和FIFO(先進先出)
    -->
    <diskStore path="D:\ehcache"/>

    <defaultCache
            maxElementsInMemory="1"
            maxElementsOnDisk="10000000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
</ehcache>
  1. EmpDao.xml
<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

11 逆向工程

  1. MyBatis提供了根據數據庫中的表,自動生成對應的實體類、bean類以及mapper映射文件的功能
  2. 具體配置文檔可以在github上的mybatis/generator中找到
  3. 數據庫表被改動,需要再次使用逆向工程生成文件時,需要先把原來的文件刪除

11.1 流程

  1. 引入pom依賴
<!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
<dependency>
    <groupId>org.mybatis.generator</groupId>
    <artifactId>mybatis-generator-core</artifactId>
    <version>1.4.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.16</version>
</dependency>
  1. 編寫逆向工程配置文件:在項目下建立即可,文件名隨意,此處爲mbg.xml
<!DOCTYPE generatorConfiguration PUBLIC
        "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
    <!--1. 具體配置的上下文環境,targetRuntime:指官網中提供的哪種反向生成方式-->
    <context id="simple" targetRuntime="MyBatis3">
        <!--2. 配置連接的數據庫,從配置的數據庫中找表-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/demo?serverTimezone=UTC"
                        userId="root"
                        password="c50hst"
        />
        <!--3. 配置實體類生成規則
                1. targetPackage:指定實體類所在的package
                2. targetProject:指定實體類在工程的哪個目錄下
        -->
        <javaModelGenerator targetPackage="com.mashibing.bean" targetProject="src/main/java"/>

        <!--4. 配置映射文件生成規則
                1. targetPackage:指定生成java文件的目錄
                2. targetProject:放在那個工程的哪個目錄下
        -->
        <sqlMapGenerator targetPackage="com.mashibing.dao" targetProject="src/main/resources"/>

        <!--5. 配置DAO接口生成規則
                1. type:指定DAO接口是按xml方式還是註解方式工作-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.mashibing.dao" targetProject="src/main/java"/>

        <!--6. 指定要逆向生成的數據表
                1. tableName:表名
                2. domainObjectName:實體類名
        -->
        <table tableName="emp" domainObjectName="Emp" enableCountByExample="false" enableDeleteByExample="false"
               enableUpdateByExample="false" selectByExampleQueryId="false" enableSelectByExample="false"/>
        <table tableName="dept" domainObjectName="Dept" enableCountByExample="false" enableDeleteByExample="false"
               enableUpdateByExample="false" selectByExampleQueryId="false" enableSelectByExample="false"/>
    </context>
</generatorConfiguration>
  1. 編寫生成類
package com.mashibing;

import org.mybatis.generator.api.MyBatisGenerator;
import org.mybatis.generator.config.Configuration;
import org.mybatis.generator.config.xml.ConfigurationParser;
import org.mybatis.generator.exception.InvalidConfigurationException;
import org.mybatis.generator.exception.XMLParserException;
import org.mybatis.generator.internal.DefaultShellCallback;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void main(String[] args) throws IOException, XMLParserException, InvalidConfigurationException, SQLException, InterruptedException {
        List<String> warnings = new ArrayList<String>();
        boolean overwrite = true;
        File configFile = new File("mbg.xml");
        ConfigurationParser cp = new ConfigurationParser(warnings);
        Configuration config = cp.parseConfiguration(configFile);
        DefaultShellCallback callback = new DefaultShellCallback(overwrite);
        MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings);
        myBatisGenerator.generate(null);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章