2. Mybatis映射文件详解

1 mybatis映射文件

1.1 顶级元素标签
标签作用
cache该命名空间的缓存配置
cache-ref引用其它命名空间的缓存配置
resultMap描述数据库结果集与java对象的映射关系,是最复杂也是最强大的元素
parameterMap老式风格的参数映射,此元素已被废弃
sql可被其它语句引用的可重用语句块
insertdao层方法与数据库中插入语句的映射关系
updatedao层方法与数据库中更新语句的映射关系
deletedao层方法与数据库中删除语句的映射关系
selectdao层方法与数据库中查询语句的映射关
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. 使用 方 式 进 行 取 值 : 采 用 的 是 直 接 跟 s q l 语 句 进 行 拼 接 的 方 式 , 当 需 要 动 态 传 入 表 名 、 列 名 时 需 要 使 用 {}方式进行取值:采用的是直接跟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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值