增删改查
在上篇笔记中,我们完成了MyBatis的环境搭建并编写了例子程序,对MyBatis的核心配置和基础用法有了初步认识。这篇笔记我们将继续深入学习MyBatis的增删改查基础操作,掌握Mapper编写、参数传递、结果映射等核心功能的使用方法。
环境准备
这里我们还是使用MySQL数据库进行演示,我们使用的表t_role如下。
CREATE TABLE t_role (
role_id BIGINT AUTO_INCREMENT PRIMARY KEY,
rolename VARCHAR(255) NOT NULL
);
数据表对应的实体类如下。
package com.gacfox.demo.model;
import lombok.Data;
@Data
public class Role {
private Long roleId;
private String rolename;
}
我们这里创建一个空的Mapper XML配置文件RoleMapper.xml,后续介绍的XML配置都将基于该例子配置文件介绍,与之关联的Mapper接口也对应创建即可,这里就不占用篇幅黏贴了。
<?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.gacfox.demo.mapper.RoleMapper">
</mapper>
查询
查询单条结果
查询单条结果时,我们的Mapper接口方法需要返回单个实体类对象,接口方法声明如下。
Role selectRoleById(Long roleId);
对应XML配置如下。
<select id="selectRoleById" resultType="com.gacfox.demo.model.Role">
SELECT role_id, rolename
FROM t_role
WHERE role_id = #{roleId}
</select>
该Mapper方法将查询单条记录,如果查询无结果则返回null,如果返回多条将抛出异常。如果我们不能保证SQL返回的结果是单条数据记录就应该使用查询多条的写法,即返回类型为List<Role>,或者在SQL中限制返回第1条。
这里我们注意以下几点:
select的id属性对应接口中的方法名resultType对应实体类的类全名,这里Role是该查询方法的返回值,但如果你在mybatis-config.xml中配置了别名,则使用别名即可- SQL语句中,
role_id会被自动映射为roleId,这是因为我们曾在mybatis-config.xml中配置过驼峰命名自动映射,如果你不开启这个功能,一种方式是将SQL写作select role_id as roleId,另一种方式是使用后文介绍的resultMap映射,两种方式推荐使用后者 - 方法声明中的参数在XML中的SQL语句里需要用
#{}包裹,里面的参数名需要和方法声明中的参数名对应 - 当Mapper方法只有一个参数时我们可以不用
@Param注解标注(上面就是这样),如果有多个参数就必须用注解标注,我们将在后文介绍
使用resultMap手动映射查询结果
之前的例子中,MyBatis框架自动映射了SQL查询的结果集到Java实体类对象上,除此之外另一种方式是使用resultMap实现手动进行映射。
<resultMap id="roleMap" type="com.gacfox.demo.model.Role">
<id property="roleId" column="role_id"/>
<result property="rolename" column="rolename"/>
</resultMap>
<select id="selectRoleById" resultMap="roleMap">
SELECT role_id, rolename
FROM t_role
WHERE role_id = #{roleId}
</select>
<resultMap>中,id用于映射主键,result用于映射普通字段。除了上面介绍的用法,在关联查询中resultMap还有级联查询相关的配置,我们将在后文介绍。
查询多条结果
查询多条结果需要返回列表类型,Mapper接口方法定义例子如下。
List<Role> selectAll();
Mapper对应XML配置如下。
<select id="selectAll" resultType="com.gacfox.demo.model.Role">
SELECT role_id, rolename
FROM t_role
</select>
这里SQL语句返回的可能是一条结果、多条结果或是没有结果。方法的返回值是一个List,返回结果可能里面有多条、1条、或是0条数据。实际上,返回一个结果时,我们也可以使用List接收返回结果(列表中仅有一条数据),然而如果不使用列表接收但返回了多条结果将抛出异常。
传递多个参数进行查询
前面我们提到过,如果Mapper方法参数有多个,就必须用@Param注解对参数起名。
List<Role> selectRoleByPage(@Param("start_index") Integer startIndex, @Param("page_size") Integer pageSize);
对应XML配置如下,其中#{}包裹的就是注解指定的参数名。
<select id="selectRoleByPage" resultType="com.gacfox.demo.model.Role">
SELECT role_id, rolename
FROM t_role
LIMIT #{start_index}, #{page_size}
</select>
这里我们传递了多个参数进行查询,注意参数需要用@Param进行标注,这个注解是org.apache.ibatis.annotations.Param包的,不要和别的包弄混。只有一个参数时@Param可以省略,SQL中的参数名是对应方法的参数名,当然,如果你有强迫症,那么所有Mapper方法参数都标注@Param也是完全可以的。
使用Map或POJO封装参数
除了直接传递基本类型,我们也可以使用Map集合或POJO作为参数载体,这也是多参数传递的另一种常用方式。下面例子代码我们使用POJO封装参数。
package com.gacfox.demo.dto;
import lombok.Data;
@Data
public class RoleQueryDto {
private String rolename;
}
<select id="selectByRolename" resultType="com.gacfox.demo.model.Role">
SELECT role_id,
rolename
FROM t_role
WHERE rolename LIKE CONCAT('%', #{rolename}, '%')
</select>
在这种情况下,如果你显式使用了@Param,写法还会有些区别,此时Mapper XML里需要显式写出@Param标注的名字,并用点号.取属性。
Role selectByRolename(@Param("roleQueryDto") RoleQueryDto roleQueryDto);
<select id="selectByRolename" resultType="com.gacfox.demo.model.Role">
SELECT role_id,
rolename
FROM t_role
WHERE rolename LIKE CONCAT('%', #{roleQueryDto.rolename}, '%')
</select>
插入
对于插入语句,我们需要传入包含数据的实体类对象,然后在Mapper映射XML中拼接INSERT插入语句。
int insertRole(Role role);
对应XML配置如下。
<insert id="insertRole" useGeneratedKeys="true" keyProperty="roleId">
INSERT INTO t_role(rolename)
VALUES (#{rolename})
</insert>
由于我们使用了MySQL的自增长主键,因此我们需要在插入后得知该条数据主键值,useGeneratedKeys="true"表示插入时使用数据库提供的主键自增长功能,keyProperty是实体类中主键对应的字段名。这里SQL中的参数字段对应实体类中的属性名,实际上这也是传入多个参数的一种方式。
注意MyBatis中执行插入操作,接口方法的返回值是受影响的行数,主键值在传入的对象中,如果我们需要获取插入后的主键值,可以使用如下写法。
Role role = new Role();
role.setRolename("新角色");
mapper.insertRole(role);
System.out.println("pk=" + role.getRoleId());
删除
删除和插入类似,我们需要传入参数并拼接DELETE语句。
int deleteRole(Long roleId);
对应XML配置如下。
<delete id="deleteRole">
DELETE
FROM t_role
WHERE role_id = #{roleId}
</delete>
对于删除方法,Mapper接口返回值也是受影响的行数。
修改
修改方法和插入、删除类似,我们需要传入参数拼接UPDATE语句。
int updateRole(Role role);
对应XML配置如下。
<update id="updateRole">
UPDATE t_role
SET rolename = #{rolename}
WHERE role_id = #{roleId}
</update>
和之前一样,Mapper接口返回值也是受影响的行数。
批量插入
如果我们要插入大量数据,逐条INSERT是不可行的,这样做效率非常低下。MyBatis中,主要有两种实现批量插入的方式,基于原生SQL拼接和基于批量执行器实现。
拼接SQL实现批量插入
SQL语句本身就支持批量插入,MyBatis中实现批量插入最简单的方式其实就是直接手动拼接批量插入语句,这需要用到动态SQL中的<foreach>标签,下面是一个例子。
int batchInsertRole(List<Role> roles);
对应XML配置如下。
<insert id="batchInsertRole">
INSERT INTO t_role (rolename)
VALUES
<foreach collection="list" item="role" separator=",">
(#{role.rolename})
</foreach>
</insert>
有关动态SQL我们将在下一章节详细介绍。
使用批量执行器实现批量插入
另一种方案是使用批量执行器实现批量插入,这种方式我们的Mapper层面仍实现简单的单条数据插入即可,批量SQL的拼接是在MyBatis框架内部实现的,下面代码演示了这一用法。
package com.gacfox.demo;
import com.gacfox.demo.mapper.RoleMapper;
import com.gacfox.demo.model.Role;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
try {
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
reader.close();
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role1 = new Role();
role1.setRolename("ADMIN");
Role role2 = new Role();
role2.setRolename("USER");
Role role3 = new Role();
role3.setRolename("GUEST");
for (Role role : Arrays.asList(role1, role2, role3)) {
roleMapper.insertRole(role);
}
sqlSession.commit();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
代码中,我们写法看似是在for循环中逐条插入,但实际上我们使用的是批量执行器,MyBatis框架会自动帮我们拼接批量插入语句,此外批量执行器要求我们必须手动提交事务,因此这里我们调用了commit(),通过控制台中打印的SQL执行日志我们可以观察并验证最终执行的SQL语句。