MyBatis 使用

MyBatis Series

MyBatis 安装与入门: https://blog.yexca.net/archives/215
MyBatis 使用: 本文

删除

使用 #{} 为占位符,内部为参数名

@Mapper
public interface EmpMapper {
    // 删除
    @Delete("delete from mybatis.emp where id = #{id}")
    public void delete(Integer id);
}

测试

@SpringBootTest
class Mybatis02ApplicationTests {
    @Autowired
    private EmpMapper empMapper;
    @Test
    void contextLoads() {
        empMapper.delete(17);
    }
}

一般不需要返回值,返回值为此次操作影响的行数

@Mapper
public interface EmpMapper {
    // 删除
    @Delete("delete from mybatis.emp where id = #{id}")
    public int delete(Integer id);
}

占位符

参数占位符有 #{}${}

占位符 #{} ${}
形式 预编译 拼接
使用时机 参数传递、登录等 对表名、列表动态设置
优劣 安全,性能高 存在 SQL 注入问题

插入 (新增)

同样使用占位符,不过若传递的参数过多可以使用对象封装,形式参数名为对象的参数名

@Mapper
public interface EmpMapper {
    // 增加
    @Insert("insert into mybatis.emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) "+
            "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
    public void insert(Emp emp);
}

测试

@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testInsert(){
        Emp emp = new Emp();
        emp.setUsername("Tom");
        emp.setName("汤姆");
        emp.setGender((short) 1);
        emp.setImage("tom.jpg");
        emp.setJob((short) 1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setDeptId(1);
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());

        empMapper.insert(emp);
    }
}

获取主键值

某些情况下在数据添加成功后,需要获取插入数据库数据的主键

@Mapper
public interface EmpMapper {
    // 获取主键,第一个属性定义主键赋值在emp对象的id属性,第二个属性代表需要获取返回主键值
    @Options(keyProperty = "id", useGeneratedKeys = true)
    // 增加
    @Insert("insert into mybatis.emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) "+
            "values (#{username},#{name},#{gender},#{image},#{job},#{entrydate},#{deptId},#{createTime},#{updateTime});")
    public void insert(Emp emp);
}

更新 (修改)

与增加类似,可以封装到一个对象里

@Mapper
public interface EmpMapper {
    // 修改
    @Update("update mybatis.emp set username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, "+
            "entrydate = #{entrydate}, dept_id = #{deptId}, update_time = #{updateTime} where id = #{id};")
    public void update(Emp emp);
}

测试

@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testUpdate(){
        Emp emp = new Emp();
        emp.setUsername("Tom2");
        emp.setName("汤姆2");
        emp.setGender((short) 1);
        emp.setImage("tom.jpg");
        emp.setJob((short) 1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setDeptId(1);
        emp.setUpdateTime(LocalDateTime.now());
        emp.setId(19);
        empMapper.update(emp);
    }
}

查询

查询有两种,根据 id 查询全部属性,根据条件查询

根据 ID 查询

接口

@Mapper
public interface EmpMapper {
    // 根据ID查询
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

测试

@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testIdSelect(){
        Emp emp = empMapper.idSelect(19);
        System.out.println(emp);
    }
}

结果

Emp(id=19, username=Tom2, password=123456, name=汤姆2, gender=1, image=tom.jpg, job=1, entrydate=2000-01-01, deptId=null, createTime=null, updateTime=null)

这样查询出来的因为类 Emp 与数据库的部分字段名字不同,从而结果为 null

有三种方法解决

使用别名

@Mapper
public interface EmpMapper {
    // 方法一:别名
    @Select("select id, username, password, name, gender, image, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime" +
            " from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

使用 @Results 注解

@Mapper
public interface EmpMapper {
    // 方法二:@Results @Result 注解
    @Results({
            @Result(column = "dept_id", property = "deptId"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "update_time", property = "updateTime")
    })
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

使用自动封装

如果字段名与属性名符合驼峰命名规则,可以开启驼峰命名自动封装,Mybatis 会自动通过驼峰命名规则映射,具体为数据库是下划线 a_column 映射到 Java 属性 aColumn

开启驼峰命名需在 application.properties 加入

mybatis.configuration.map-underscore-to-camel-case=true

然后直接使用最初的代码

@Mapper
public interface EmpMapper {
    // 根据ID查询
    @Select("select * from mybatis.emp where id = #{id}")
    public Emp idSelect(Integer id);
}

为了方便使用,可以在 IDEA 中安装 MyBatisX 插件

根据条件查询

需求:根据员工姓名 (模糊匹配)、性别 (精准匹配)、入职时间 (范围) 搜索满足条件的员工信息

@Mapper
public interface EmpMapper {
    // 根据条件查询
    // 注意此处name like '%${name}%' 使用的${},可以在''内使用
    @Select("select * from mybatis.emp where name like '%${name}%' and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

因为使用了插值,进行字符串拼接,所以此方法不安全

@Mapper
public interface EmpMapper {
    // 更安全的条件查询
    @Select("select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

使用了 concat 函数拼接字符串。测试

@SpringBootTest
class Mybatis02ApplicationTests {
    
    @Autowired
    private EmpMapper empMapper;
    
    @Test
    public void testConditionSelect(){
        List<Emp> empList = empMapper.conditionSelect("张", (short) 1, LocalDate.of(2010, 1, 1), LocalDate.of(2020, 1, 1));
        System.out.println(empList);
    }
}

参数名说明

在 SQL 中使用的 #{} 中的变量名在 SpringBoot 2.x 以后的版本会被自动识别,但在 1.x 版本或者单独使用 MyBatis 时方法定义的变量名需要使用注解指定才可被识别

@Mapper
public interface EmpMapper {
    // 更安全的条件查询
    @Select("select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and " +
            "entrydate between #{begin} and #{end} order by update_time desc")
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

// 上述代码在1.x或单独使用MyBatis时不生效,函数要添加注解
public List<Emp> conditionSelect(@Param("name") String name, @Param("gender") short gender, @Param("begin") LocalDate begin, @Param("end") LocalDate end);

使用 XML 映射文件

如果 SQL 代码比较简短使用注解很方便,但若是 SQL 较长或复杂则会显得稍乱,为此可以使用 XML 映射文件。以下为规范

  • XML 映射文件的名称与 Mapper 接口名称一致,并且将 XML 映射文件和 Mapper 接口放在相同包下 (同包同名)
  • XML 中 namespace 属性为 Mapper 接口全限定名一致
  • XML 中 SQL 语句的 id 属性与 Mapper 接口中方法名一致,并且返回类型一致

XML

在 Maven 工程,非 Java 文件放在 src/main/resources 下,在此目录下创建与 Mapper 接口一致的目录

如接口为 net.yexca.mapper.EmpMapper.java ,则 XML 文件在 net.yexca.mapper.EmpMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 上方头文件到官方网站寻找 -->
<!-- namespace属性为接口 -->
<mapper namespace="net.yexca.mapper.EmpMapper">
    <!--  id属性为方法名 -->
    <!--  resultType为单条记录所封装的类型 -->
    <select id="conditionSelect" resultType="net.yexca.pojo.Emp">
        select * from mybatis.emp where name like concat('%',#{name},'%') and gender = #{gender} and
        entrydate between #{begin} and #{end} order by update_time desc
    </select>
</mapper>

接口类

@Mapper
public interface EmpMapper {
    public List<Emp> conditionSelect(String name, short gender, LocalDate begin, LocalDate end);
}

动态 SQL

动态 SQL 为随着用户的输入或外部条件的变化而变化的 SQL 语句

例如上述最后一个条件查询,必须输入全部三个条件才能查询,可实际使用或许可以仅指定其中一两个或不指定 (查询全部),如果按照上述指令执行将返回空结果

where if

用于判断条件是否成立,使用 test 属性进行条件判断,如果条件为 true,则拼接 SQL

例如,当姓名不为空时,拼接条件通过姓名查询

<if test="name!=null">
	name like contact('%',#{name},'%')
</if>

而 where 标签则管理是否生成 where 关键字并自动去除 and 和 or 关键字

<select id="conditionSelect" resultType="net.yexca.pojo.Emp">
        select * from mybatis.emp
        <where>
            <if test="name!=null">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender!=null">
                and gender = #{gender}
            </if>
            <if test="begin!=null and end!=null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>

set

替换 SQL 中 set 关键字,并自动去除多余的逗号,用于 UPDATE 语句中

例如以下 SQL 语句修改为动态 SQL

update mybatis.emp
    set username    = #{username},
        name        = #{name},
        gender      = #{gender},
        image       = #{image},
        job         = #{job},
        entrydate   = #{entrydate},
        dept_id     = #{deptId},
        update_time = #{updateTime}
    where id = #{id};

XML

<update id="update2">
    update mybatis.emp
    <set>
        <if test="username != null">username = #{username},</if>
        <if test="name != null">name = #{name},</if>
        <if test="gender != null">gender = #{gender},</if>
        <if test="image != null">image = #{image},</if>
        <if test="job != null">job = #{job},</if>
        <if test="entrydate != null">entrydate = #{entrydate},</if>
        <if test="deptId != null">dept_id = #{deptId},</if>
        <if test="updateTime != null">update_time = #{updateTime}</if>
    </set>
    where id = #{id};
</update>

foreach

foreach 标签用于遍历

      <!--遍历的集合    遍历出来的元素  分隔符     开始前后拼接的SQL片段-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
    #{id}
</foreach>

假如 ids 为 [13, 14, 15],以上将生成 (13,14,15)

需求,遍历删除 id 为 1,2,3。SQL 语句为

delete from emp where id in(1,2,3)

接口方法

public void deleteByIds(List<Integer> ids);

XML

<delete id="deleteByIds">
    delete from mybatis.emp where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

测试

@Test
public void testDeleteByIds(){
    List<Integer> ids = Arrays.asList(13,14,15);
    empMapper.deleteByIds(ids);
}

sql 与 include

在开发过程中可能会出现大量重复的 SQL 语句,可以同 sql 标签定义可重用的 SQL 片段,再通过 include 标签重用

<!-- 通过id属性唯一标识语句 -->
<sql id = "commonCode">
	<!-- SQL语句 -->
</sql>

<!-- 通过refid属性指定引用的语句 -->
<include refid = "commonCode" />