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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章