10.3 MyBatis调用存储过程 10.3.4修改数据

在之前创建的mybatis数据库中创建一个修改tb_user表数据的存储过程。SQL脚本如下所示:

drop procedure if exists update_user_by_id;
delimiter $$
create procedure update_user_by_id (
    IN p_id int,
    IN p_name varchar(18),
    IN p_sex varchar(19),
    IN p_age int
)
begin
    update tb_user set name=p_name,sex=p_sex,age=p_age
    where id=p_id;
end
$$
delimiter ;

注意

注意存储过程的参数定义,如果参数类型是varchar则一定要指定长度:IN p_name varchar(18),,如果直接写IN p_name varchar,将会创建失败.
也就是说下面的的将SQL脚本创建失败:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
drop procedure if exists update_user_by_id;
delimiter $$
create procedure update_user_by_id (
IN p_id int,
IN p_name varchar,
IN p_sex varchar(19),
IN p_age int
)
begin
update tb_user set name=p_name,sex=p_sex,age=p_age
where id=p_id;
end
$$
delimiter ;

错误提示如下:

1
2
3
4
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '
IN p_sex varchar(19),
IN p_age int
)

xml映射

1
2
3
4
5
6
7
8
9
10
11
<update
id="updateUserById"
parameterType="domain.User"
statementType="CALLABLE"> {
call update_user_by_id(
#{id,mode=IN},
#{name,mode=IN},
#{sex,mode=IN},
#{age,mode=IN})
}
</update>

mapper接口方法

1
void updateUserById(User user);

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package test;
import org.apache.ibatis.session.SqlSession;
import domain.User;
import fractory.SqlSessionFratoryTools;
import mapper.UserMapper;
public class UpdateUserByIDTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
try
{
// 2.获取Mapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 3.调用mapper接口方法进行操作
// 先找到要更新的记录
User user = userMapper.selectUserById(6);
if (user != null)
{
System.out.println("要更新的用户:" + user);
user.setName("小李子");
user.setSex("女");
user.setAge(18);
// 更新用户的信息
userMapper.updateUserById(user);
user = userMapper.selectUserById(6);
System.out.println("更新后用户的信息:"+user);
// 5.提交事务
sqlSession.commit();
}
} catch (Exception e)
{
// 6.出错回滚
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 7.关闭会话
if (sqlSession != null)
{
sqlSession.close();
}
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
DEBUG [main] ==>  Preparing: {call select_user_by_id(?)} 
DEBUG [main] ==> Parameters: 6(Integer)
DEBUG [main] <== Total: 1
DEBUG [main] <== Updates: 0
要更新的用户:User [id=6, name=小李, sex=男, age=23]
DEBUG [main] ==> Preparing: { call update_user_by_id( ?, ?, ?, ?) }
DEBUG [main] ==> Parameters: 6(Integer), 小李子(String), 女(String), 18(Integer)
DEBUG [main] <== Updates: 1
DEBUG [main] ==> Preparing: {call select_user_by_id(?)}
DEBUG [main] ==> Parameters: 6(Integer)
DEBUG [main] <== Total: 1
DEBUG [main] <== Updates: 0
更新后用户的信息:User [id=6, name=小李子, sex=女, age=18]

10.3 MyBatis调用存储过程 10.3.3根据id查询数据返回对象

创建存储过程

在之前创建的mybatis数据库中创建一个根据id查询tb_user表对应数据的存储过程,SQL脚本如下:

1
2
3
4
5
6
7
8
drop procedure if exists select_user_by_id;
delimiter $$
create procedure select_user_by_id(IN in_id INTEGER)
begin
select id,name,sex,age from tb_user where id=in_id;
end
$$
delimiter ;

创建映射标签

UserMapper.xml中加入调用名为”select_user_by_id“的存储过程的select元素,如下代码所示:

1
2
3
4
5
6
7
8
<select
id="selectUserById"
parameterType="int"
resultType="domain.User"
statementType="CALLABLE"> {
call select_user_by_id(#{id,mode=IN})
}
</select>

mapper接口方法

1
User selectUserById(Integer id);

测试类

/MyProcedureTest/src/test/SelectByIdTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package test;

import org.apache.ibatis.session.SqlSession;
import domain.User;
import fractory.SqlSessionFactoryTools;
import mapper.UserMapper;

public class SelectByIdTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFactoryTools.getSqlSession();
try {
// 2.获取Mapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 3.调用mapper接口方法进行操作
User user = userMapper.selectUserById(1);
System.out.println(" " + user);
// 5.提交事务
sqlSession.commit();
} catch (Exception e) {
// 6.出错回滚
sqlSession.rollback();
e.printStackTrace();
} finally {
// 7.关闭会话
if (sqlSession != null) {
sqlSession.close();
}
}
}
}

运行效果

1
2
3
4
5
DEBUG [main] ==>  Preparing: {call select_user_by_id(?)} 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
DEBUG [main] <== Updates: 0
User [id=1, name=小明, sex=男, age=21]

10.3 MyBatis调用存储过程 10.3.2查询数据返回集合

项目的创建请看上一篇.

创建存储过程

在之前创建的mybatis数据库中创建一个查询tb_user表所有数据的存储过程。SQL脚本如下:

1
2
3
4
5
6
7
8
drop procedure if exists select_user;
delimiter $$
create procedure select_user()
begin
select id,name,sex,age from tb_user;
end
$$
delimiter ;

mapper.xml映射

<select
    id="selectUser"
    resultType="domain.User"
    statementType="CALLABLE"> {call select_user()}
</select>

select标签调用名为”select_user“的存储过程查询所有User数据并返回List,查询到的每一条数据会被封装到User对象中,这和之前执行SQL语句返回数据的方式完全一致.

mapper接口方法

1
List<User> selectUser();

测试类

/MyProcedureTest/src/test/SelectTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SelectTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFactoryTools.getSqlSession();
try {
// 2.获取Mapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 3.调用mapper接口方法进行操作
List<User> users = userMapper.selectUser();
users.forEach(user -> {
System.out.println(" " + user);
});
// 5.提交事务
sqlSession.commit();
} catch (Exception e) {
// 6.出错回滚
sqlSession.rollback();
e.printStackTrace();
} finally {
// 7.关闭会话
if (sqlSession != null) {
sqlSession.close();
}
}
}
}

运行测试类,测试selectUser方法将调用存储过程查询所有用户信息。控制台显示如下:

1
2
3
4
5
6
7
8
9
10
DEBUG [main] ==>  Preparing: {call select_user()} 
DEBUG [main] ==> Parameters:
DEBUG [main] <== Total: 6
DEBUG [main] <== Updates: 0
User [id=1, name=小明, sex=男, age=21]
User [id=2, name=小王, sex=男, age=22]
User [id=3, name=小丽, sex=女, age=18]
User [id=4, name=小芳, sex=女, age=18]
User [id=5, name=小王, sex=男, age=22]
User [id=6, name=小李, sex=男, age=23]

10.3 MyBatis调用存储过程

SQL语句在执行的时候需要先编译,然后执行。数据库的存储过程(Procedure)是组为了完成特定功能的SQL语句,编译后存储在数据库中,用户通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用存储过程。
使用存储过程可提高数据库执行速度,并且存储过程可以重复使用,从而减少数据库开发人员的工作量。
下面重点介绍如何使用MyBatis调用存储过程。

10.3.1插入数据

测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 取消外键约束
SET FOREIGN_KEY_CHECKS = 0;
# 创建数据表
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(18) DEFAULT NULL,
`sex` char(2) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
# 插入数据
INSERT INTO `tb_user` VALUES ('1', '小明', '男', '21');
INSERT INTO `tb_user` VALUES ('2', '小王', '男', '22');
INSERT INTO `tb_user` VALUES ('3', '小丽', '女', '18');
INSERT INTO `tb_user` VALUES ('4', '小芳', '女', '18');
# 开启外键约束
SET FOREIGN_KEY_CHECKS = 1;

创建存储过程

在之前创建的mybatis数据库中创建一个插入tb_user表数据的存储过程,SQL脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
drop procedure if exists insert_user;
DELIMITER $$
CREATE PROCEDURE insert_user (
OUT v_id int,
in v_name varchar(18),
in v_sex varchar(19),
in v_age int
)
BEGIN
INSERT INTO tb_user (name,sex,age) VALUES (v_name,v_sex,v_age);
SET v_id=LAST_INSERT_ID();
END
$$
DELIMITER ;

存储过程代码详解

以上存储过程代码解释如下:

  • DELIMITERMySQL解释器,用于规定SQL语句的结束符,默认情况SQL语句的结束符是分号(;).为了录入存储过程中的SQL语句,我们需要先把默认的结束符(;)换成其他字符,录入结束后再把结束符换回分号.
  • 在存储过程中使用参数时,除了参数名和参数类型之外,还建议指定参数的mode(模式),可选值为INOUTINOUT三种。
    • 输入参数使用IN,
    • 输出参数(返回)使用OUT,
    • 输入输出参数使用 INOUT
  • LAST_INSERT_IDMySQL的函数,和AUTO_INCREMENT属性一起使用,当往带有AUTO_INCREMENT属性字段的表中新增数据时, LAST_INSERT_ID函数返回该字段的值。

命令行调用存储过程

调用示例如下:

1
call insert_user(@output,'小王','男',22);

第一个参数@output是一个变量,用来接收存储过程的输出.
其三个参数是存储过程的输入.

显示存储过程的输出

select 输出变量名;即可显示存储过程的输出,如下代码所示:

1
select @output;

运行结果如下:

1
2
3
4
5
6
7
8
9
mysql> call insert_user(@output,'小王','男',22);
Database changed
mysql> select @output;
+---------+
| @output |
+---------+
| 5 |
+---------+
1 row in set

此时tb_user表中的数据如下:

1
2
3
4
5
6
7
8
9
10
11
mysql> select * from tb_user;
+----+------+-----+-----+
| id | name | sex | age |
+----+------+-----+-----+
| 1 | 小明 | 男 | 21 |
| 2 | 小王 | 男 | 22 |
| 3 | 小丽 | 女 | 18 |
| 4 | 小芳 | 女 | 18 |
| 5 | 小王 | 男 | 22 |
+----+------+-----+-----+
5 rows in set

创建持久化对象

/MyProcedureTest/src/domain/User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package domain;
public class User {
private Integer id;
private String name;
private String sex;
private Integer age;
public User()
{
super();
}
// 此处省略getter和setter方法,请自己补上
@Override
public String toString()
{
return "User [id=" + id + ", name=" + name + ", sex=" + sex + ", age=" + age + "]";
}
}

编写Mapper.xml映射文件

<insert
    id="inserUser"
    parameterType="domain.User"
    statementType="CALLABLE"> {
    call insert_user(
        #{id,mode=OUT,jdbcType=INTEGER},
        #{name,mode=IN},
        #{sex,mode=IN},
        #{age,mode=IN}
    ) }
</insert>

insert标签调用名为”insert_user“的存储过程完成一个插入数据的操作。

调用存储过程时,需要把statementType属性的值设置为”CALLABLE“。

call“是用来调用存储过程的关键字,
需要注意的是,OUT模式的参数必须指定jdbcType,这是因为在IN模式下,MyBatis提供了默认的jdbcType,而在OUT模式下没有提供。

注意id列的参数jdbcType不要写成javaType.

编写mapper接口

/MyProcedureTest/src/mapper/UserMapper.java
1
2
3
4
5
package mapper;
import domain.User;
public interface UserMapper {
void inserUser(User user);
}

编写测试类

/MyProcedureTest/src/test/InserTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package test;
import domain.User;
import org.apache.ibatis.session.SqlSession;
import fractory.SqlSessionFratoryTools;
import mapper.UserMapper;
public class InserTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
try
{
// 2.获取Mapper接口的代理对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setAge(23);
user.setName("小李");
user.setSex("男");
// 4.调用mapper接口方法进行操作
userMapper.inserUser(user);
// 5.提交事务
sqlSession.commit();
System.out.println("自动生成的Id:" + user.getId());
} catch (Exception e)
{
// 6.出错回滚
sqlSession.rollback();
e.printStackTrace();
} finally
{
// 7.关闭会话
if (sqlSession != null)
{
sqlSession.close();
}
}
}
}

运行效果

此时tb_user表中的数据如下:

1
2
3
4
5
6
7
8
9
10
11
mysql> select * from tb_user;
+----+------+-----+-----+
| id | name | sex | age |
+----+------+-----+-----+
| 1 | 小明 | 男 | 21 |
| 2 | 小王 | 男 | 22 |
| 3 | 小丽 | 女 | 18 |
| 4 | 小芳 | 女 | 18 |
| 5 | 小王 | 男 | 22 |
+----+------+-----+-----+
5 rows in set

运行测试类,inserUser()方法将会插入一个用户记录到数据库表中,并输出自动生成的id值。控制台显示如下:

1
2
3
4
DEBUG [main] ==>  Preparing: { call insert_user( ?, ?, ?, ? ) } 
DEBUG [main] ==> Parameters: 小李(String), 男(String), 23(Integer)
DEBUG [main] <== Updates: 1
自动生成的Id:6

可以看到,MyBatis调用了名为”insert_user“的存储过程,并且传入了4个参数.其中第一个参数id用来接收存储过程的输出.所以尽管我们没有给user对象的id属性赋值,但是存储过程插入成功后,id属性的值变成了6.
查询数据库,可以看到tb_user表中插入了一条新的记录,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
mysql> select * from tb_user;
+----+------+-----+-----+
| id | name | sex | age |
+----+------+-----+-----+
| 1 | 小明 | 男 | 21 |
| 2 | 小王 | 男 | 22 |
| 3 | 小丽 | 女 | 18 |
| 4 | 小芳 | 女 | 18 |
| 5 | 小王 | 男 | 22 |
| 6 | 小李 | 男 | 23 |
+----+------+-----+-----+
6 rows in set

10.2 MyBatis动态SQL 10.2.6 bind标签

bind标签元素可以从OGNL表达式创建一个变量并将其绑定到上下文。

实例

mapper接口方法

1
List<Employee> seletEmployeeLikeName(Employee employee);

Mapper.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 测试bind属性 -->
<select
id="seletEmployeeLikeName"
resultType="domain.Employee">
<!-- 创建OGNL表达式并绑定到上下文的pattern属性中 -->
<!-- _parameter表示传递的Employee对象 -->
<!-- _parameter.getName()表示调用参数(Employee对象)的getName方法 -->
<!-- 两个下划线`__`是like子句的通配符,一个下划线可以匹配一个字符,两个下划线表示可以匹配两个字符 -->
<!-- like子句还有一个通配符%(百分号)这个通配符可以匹配任意多个字符 -->
<!-- 整个表达式表示匹配以_parameter.getName()获取到的员工名字开通,并且后面还有两个任意字符的字符串 -->
<bind
name="pattern"
value="_parameter.getName()+'__'"/>
<!-- 通过mybatis表达式获取上下文中的pattern属性值 -->
select * from tb_employee where loginname like #{pattern}
</select>

bind标签解释

创建OGNL表达式并绑定到上下文的pattern属性中

  • _parameter表示seletEmployeeLikeName方法传递的参数(Employee对象)
  • _parameter.getName()表示调用参数的getName方法,也就是,调用Employee对象的getName()方法
  • 两个下划线__like子句的通配符,
    • 一个下划线可以匹配一个字符,
    • 两个下划线表示可以匹配两个字符
    • like子句还有一个通配符%(百分号)这个通配符可以匹配任意多个字符
  • 整个表达式表示匹配以_parameter.getName()获取到的员工名字开头,并且后面还有两个任意字符的字符串

测试类

/MyDynamicSQLTest/src/test/BindTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BindTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee find = new Employee();
find.setName("xiao");
List<Employee> employees = employeeMapper.seletEmployeeLikeName(find);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

运行结果:

1
2
3
4
5
DEBUG [main] ==>  Preparing: select * from tb_employee where loginname like ? 
DEBUG [main] ==> Parameters: xiao__(String)
DEBUG [main] <== Total: 1
Employee [id=3, loginname=xiaoli, password=xiaoli, name=小丽, sex=女, age=23, phone=123456789123, sal=7800.0, state=active]

可以看到,程序已经成功查询出登录名以xiao开头,并且后面只有两个字符的员工.

10.2 MyBatis动态SQL 10.2.5 foreach标签

关于动态SQL另外一个常用的操作就是需要对一个集合进行遍历,通常发生在构建in条件语句时。

xml映射

1
2
3
4
5
6
7
8
9
10
11
12
13
<select
id="seletEmployeeInIdList"
resultType="domain.Employee">
select * from tb_employee where id in
<foreach
item="item"
index="index"
collection="list"
open="("
separator=","
close=")"> #{item}
</foreach>
</select>

foreach标签的功能非常强大,它允许指定一个集合,声明可以用在标签体内的集合项和索引变量。它也允许指定开闭匹配的字符串以及在迭代中间放置分隔符。这个标签是很智能的,因此它不会随机地附加多余的分隔符。

接口方法

1
List<Employee> seletEmployeeInIdList(List<Integer> ids);

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ForEachTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
List<Employee> employees = employeeMapper.seletEmployeeInIdList(ids);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

运行效果

DEBUG [main] ==>  Preparing: SELECT * FROM tb_employee WHERE ID in ( ? , ? , ? ) 
DEBUG [main] ==> Parameters: 1(Integer), 2(Integer), 3(Integer)
DEBUG [main] <==      Total: 3
    Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]
    Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]
    Employee [id=3, loginname=xiaoli, password=xiaoli, name=小丽, sex=女, age=23, phone=123456789123, sal=7800.0, state=active]

可以看到,执行的SQL语句是一个in条件语句,返回的是List集合中的id1,2,3的员工数据。

foreach标签解释

  • 标签属性:
    • collection="list"表示参数类型是List
    • item当前遍历的元素
    • index当前遍历的下标,
      • foreach遍历的数list或者数组时,index代表就是下标,
      • foreach遍历mapindex代表key,此时item表示value
    • open表示前缀,在遍历开始的地方显示该字符
    • separator表示分割符,用于分隔每个元素
    • close表示结束符,遍历结束时显示
  • 标签体:#{item}表示取出当前遍历的元素.

参考链接

https://www.jianshu.com/p/c9c4a2b95400

10.2 MyBatis动态SQL 10.2.4 set标签

关于动态更新语句还可以使用set标签。set标签可以用于动态包含需要更新的列,而舍去其他的。

XML映射文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!-- 根据员工Id查询员工信息 -->
<select
id="selectEmployeeById"
parameterType="int"
resultType="domain.Employee"> select * from tb_employee where id=#{id};
</select>
<!-- 测试set标签 -->
<update
id="updateEmployeeUseSet"
parameterType="domain.Employee">
update tb_employee
<set>
<if test="loginname!=null">loginname=#{loginname}</if>
<if test="password!=null">,password=#{password}</if>
<if test="name!=null">,name=#{name}</if>
<if test="sex!=null">,sex=#{sex}</if>
<if test="age!=null">,age=#{age}</if>
<if test="phone!=null">,phone=#{phone}</if>
<if test="sal!=null">,sal=#{sal}</if>
<if test="state!=null">,state=#{state}</if>
</set>
where id=#{id}
</update>

注意在if子语句中添加逗号

注意,update语句的set子句中,如果要更新多个列,则需要用逗号隔开多个列.所以要在第二个if语句及其后续的if语句中中添加逗号作为分隔符.

set标签的作用

set标签会动态前置set关键字,同时也会消除无关的逗号,因为使用了条件语句之后很可能就会在生成的赋值语句的后面留下这些逗号。

接口方法

1
2
Employee selectEmployeeById(Integer id);
void updateEmployeeUseSet(Employee employee);

需要注意的是,updateEmployeeUseSet方法传递的参数不是之前使用的HashMap,而是一个Employee对象,因为通常在进行更新操作时都是先查询出一个实体对象再进行更新操作

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SetTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmployeeById(1);
System.out.println("查询到的员工信息:" + employee);
if (employee != null)
{
employee.setName("新名字");
employee.setSal(123);
employeeMapper.updateEmployeeUseSet(employee);
sqlSession.commit();
}
employee = employeeMapper.selectEmployeeById(1);
System.out.println("更新后的员工信息:" + employee);
}
}

运行结果

运行测试类,控制台输出如下:

DEBUG [main] ==>  Preparing: select * from tb_employee where id=?; 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <==      Total: 1
查询到的员工信息:Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]
DEBUG [main] ==>  Preparing: update tb_employee SET loginname=? ,password=? ,name=? ,sex=? ,age=? ,phone=? ,sal=? ,state=? where id=? 
DEBUG [main] ==> Parameters: xiaoming(String), xiaoming(String), 新名字(String), 男(String), 19(Integer), 123456789123(String), 123.0(Double), active(String), 1(Integer)
DEBUG [main] <==    Updates: 1
DEBUG [main] ==>  Preparing: select * from tb_employee where id=?; 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <==      Total: 1
更新后的员工信息:Employee [id=1, loginname=xiaoming, password=xiaoming, name=新名字, sex=男, age=19, phone=123456789123, sal=123.0, state=active]

可以看到,测试类中,

  • 首先执行了一条查询语句,查询id1的员工,
  • 之后执行了一条update语句,根据传入的Employee对象更新员工信息。
  • 然后再查询该员工信息.可以看到已经更新成功了.

10.2 MyBatis动态SQL 10.2.3 where标签

前面几个例子已经很好地解决了动态SQL问题。现在回到之前的if示例,这次我们将state='ACTIVE'也设置成动态的条件,看看会发生什么。

1
2
3
4
5
6
7
<select
id="selectEmployeeByStateIfId"
resultType="domain.Employee">
select * from tb_employee where
<if test="state!=null">state=#{state}</if>
<if test="id!=null">and id=#{id}</if>
</select>

如果传入state参数,则执行正常。
如果没有传入参数,则会执行SQL语句:

1
select * from tb_employee where 

如果只是传入id,则会执行SQL语句:

1
select * from tb_employee where and id=? 

也就是说,如果没有传入state参数,会导致执行失败。这个问题不能简单地用条件语句来解决。MyBatis有一个简单的处理方法,只要简单地修改就能得到想要的效果.

where标签实例

EmployeeMapper.xml

EmployeeMapper.xml中添加如下标签.

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 使用where标签 -->
<select id="selectEmployeeUserWhere"
resultType="org.fkit.domain.Employee">
SELECT * FROM tb_employee
<where>
<if test="state != null "> state = #{state}</if>
<if test="id != null "> and id = #{id}</if>
<if test="loginname != null and password != null">
and loginname = #{loginname} and password = #{password}
</if>
</where>
</select>

where元素知道只有在一个以上的if条件有值的情况下才会插入where子句。而且,若最后的内容是ANDOR开头,where元素会去除多余的ANDOR

EmployeeMapper接口

EmployeeMapper接口中添加如下方法:

1
List<Employee> selectEmployeeUserWhere(HashMap<String, Object> params);

测试类

/MyDynamicSQLTest/src/test/WhereTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class WhereTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("state", "active");
params.put("id", 1);
params.put("loginname", "xiaowang");
params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUseWhere(params);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

设置所有参数

运行测试类,控制台输出如下:

DEBUG [main] ==>  Preparing: select * from tb_employee WHERE state=? and id=? and loginname=? and password=? 
DEBUG [main] ==> Parameters: active(String), 1(Integer), xiaowang(String), xiaowang(String)
DEBUG [main] <==      Total: 0

这里我们设置了state,id,loginnamepassword四个参数,执行的SQL语句为:select * from tb_employee WHERE state=? and id=? and loginname=? and password=?

设置部分参数

注释掉main方法中的state属性,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
// params.put("state", "active");
params.put("id", 1);
params.put("loginname", "xiaowang");
params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUseWhere(params);
employees.forEach(employee -> System.out.println(" " + employee));
}

再次运行控制台输出如下:

DEBUG [main] ==>  Preparing: select * from tb_employee WHERE id=? and loginname=? and password=? 
DEBUG [main] ==> Parameters: 1(Integer), xiaowang(String), xiaowang(String)
DEBUG [main] <==      Total: 0

此时我们设置了id,loginname,password这三个参数,这三个参数满足where标签中的if标签的条件,则Mybatis会生成where子句,并将if标签体中的代码作为where子句的条件.
得到的SQL语句为:select * from tb_employee WHERE id=? and loginname=? and password=?

接着注释掉id参数,再次运行控制台输出如下:

DEBUG [main] ==>  Preparing: select * from tb_employee WHERE loginname=? and password=? 
DEBUG [main] ==> Parameters: xiaowang(String), xiaowang(String)
DEBUG [main] <==      Total: 1
    Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]

可以看到执行的SQL语句为select * from tb_employee WHERE loginname=? and password=?

不传入参数

注释掉所有参数,再次运行,控制台输出如下:

DEBUG [main] ==>  Preparing: select * from tb_employee 
DEBUG [main] ==> Parameters: 
DEBUG [main] <==      Total: 4
    Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]
    Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]
    Employee [id=3, loginname=xiaoli, password=xiaoli, name=小丽, sex=女, age=23, phone=123456789123, sal=7800.0, state=active]
    Employee [id=4, loginname=xiaofang, password=xiaofang, name=小芳, sex=女, age=22, phone=123456789123, sal=8800.0, state=active]

可以发现,不传入参数时,where标签中没有任何匹配的参数,这时,生成的SQL语句中将不带有where子句。

10.2 MyBatis动态SQL 10.2.2 choose(when, otherwise)标签

MyBatis提供了choose标签,它有点像Java中的switch语句。

测试choose标签

select标签

EmployeeMapper.xml中添加入下select标签:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 测试choose标签 -->
<select id="selectEmployeeChoose" parameterType="hashmap"
resultType="org.fkit.domain.Employee">
SELECT * FROM tb_employee WHERE state = 'ACTIVE'
<!-- 如果传入了id,就根据id查询,没有传入id就根据loginname和password查询,否则查询sex等于男的数据 -->
<choose>
<when test="id != null"> and id = #{id}</when>
<when test="loginname != null and password != null">
and loginname = #{loginname} and password = #{password}
</when>
<otherwise> and sex = '男'</otherwise>
</choose>
</select>

这个select标签表示,调用该方法时

  • 如果提供了id参数就按id查找,
  • 如果提供了loginnamepassword参数就按loginnamepassword查找,
  • 如果两者都没有提供,就返回所有sex等于Employee.

mapper接口方法

在接口中对应的方法:

1
List<Employee> selectEmployeeUserChoose(HashMap<String, Object> params);

测试

提供多个条件时的运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ChooseTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("id", 1);
params.put("loginname", "xiaowang");
params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUserChoose(params);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

运行程序,控制台输出如下:

1
2
3
4
DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]

可以看到虽然两个条件都提供,但只会执行第一个条件.也就是说,choose标签中,写在前面的when标签优先匹配.并且匹配成功之后不会再匹配.
所以当满足多个条件时,只会执行第一个when标签中的SQL语句.这和只提供第一个条件的效果是一样的.

只提供第一个条件时的运行效果

现在注释掉loginnamepassword参数,保留id参数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("id", 1);
// params.put("loginname", "xiaowang");
// params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUserChoose(params);
employees.forEach(employee -> System.out.println(" " + employee));
}

再次执行,控制台输出如下:

1
2
3
4
DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <== Total: 1
Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]

可以看到这和上面提供多个条件的效果一致。

只提供第二个条件时的运行效果

注释掉id参数,保留loginnamepassword参数,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
// params.put("id", 1);
params.put("loginname", "xiaowang");
params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUserChoose(params);
employees.forEach(employee -> System.out.println(" " + employee));
}

运行效果:

1
2
3
4
DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and loginname = ? and password = ? 
DEBUG [main] ==> Parameters: xiaowang(String), xiaowang(String)
DEBUG [main] <== Total: 1
Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]

可以看到,HashMap里面传递的参数只包括loginnamepassword,所以SQL语句是按照loginnamepassword查找,查询返回的Emplyee对象就是loginnamexiaowang,并且passwordxiaowang的对象.

不提供参数时的运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
// params.put("id", 1);
// params.put("loginname", "xiaowang");
// params.put("password", "xiaowang");
List<Employee> employees = employeeMapper.selectEmployeeUserChoose(params);
employees.forEach(employee -> System.out.println(" " + employee));
}

运行效果:

1
2
3
4
5
6
DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and sex='男' 
DEBUG [main] ==> Parameters:
DEBUG [main] <== Total: 2
Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]
Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]

可以看到,由于没有传递任何参数,故SQL语句执行的是otherwise标签里面的查询条件,即”and sex='男'“,查询返回的Emplyee对象就是”sex='男'“的对象.

10.2 MyBatis动态SQL 10.2.1 if标签

10.2.1 if标签

动态SQL通常会做的事情是有条件地包含where子句的一部分。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?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="mapper.EmployeeMapper">
<!-- if单个条件 -->
<select id="selectEmployeeByStateIfId"
resultType="org.fkit.domain.Employee">
SELECT * FROM tb_employee WHERE state = 'ACTIVE'
<!-- 可选条件,如果传进来的参数有id属性,则加上id查询条件 -->
<if test="id != null "> and id = #{id}
</if>
</select>
</mapper>

以上语句提供了一个可选的根据id查找Employee的功能。

  • 如果没有传入id,那么所有处于active状态的Employee都会被返回;
  • 反之传入了id,那么所有处于active状态以及指定id内容的Employee结果返回。
1
2
3
public interface EmployeeMapper {
List<Employee> selectEmployeeByStateIfId(HashMap<String, Object> params);
}

以上代码提供了一个和EmployeeMapper.xml中的select元素的id同名的方法,需要注意的是,selectEmployeeByStateIfId接受一个HashMap作为参数。

mybatis参数获取方式

MyBatis中,#{id}表达式获取参数有两种方式:

  • 一是从HashMap中获取集合的property对象;
  • 二是从Javabean中获取property对象;

测试if标签

/MyDynamicSQLTest/src/test/OneIfTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package test;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import domain.Employee;
import fractory.SqlSessionFratoryTools;
import mapper.EmployeeMapper;
public class OneIfTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("id", 1);
List<Employee> employees = employeeMapper.selectEmployeeByStateIfId(params);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

运行结果

运行OneIfTest类的main方法,main方法中通过SqlSessiongetMapper(Class<T> type)方法获得mapper接口的代理对象EmployeeMapper。调用selectEmployeeByStateIfId方法时会执行EmployeeMapper.xmlid="selectEmployeeByStateIfId"select元素中定义的SQL语句。控制台显示如下:

DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and id=? 
DEBUG [main] ==> Parameters: 1(Integer)
DEBUG [main] <==      Total: 1
    Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]

可以看到,执行的SQL语句中因为传入了id属性,所以SQL语句中包含了”and id=?“,查询返回state=ACTIVE并且id1的数据。只得到一个id1Emplyee对象。

不传入参数的情况

接下来注释main方法中往HashMap中设置键值对的put方法,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
// params.put("id", 1);
List<Employee> employees = employeeMapper.selectEmployeeByStateIfId(params);
employees.forEach(employee -> System.out.println(" " + employee));
}
运行效果

再次执行main方法,控制台显示如下:

DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' 
DEBUG [main] ==> Parameters: 
DEBUG [main] <==      Total: 4
    Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]
    Employee [id=2, loginname=xiaowang, password=xiaowang, name=小王, sex=男, age=21, phone=123456789123, sal=6800.0, state=active]
    Employee [id=3, loginname=xiaoli, password=xiaoli, name=小丽, sex=女, age=23, phone=123456789123, sal=7800.0, state=active]
    Employee [id=4, loginname=xiaofang, password=xiaofang, name=小芳, sex=女, age=22, phone=123456789123, sal=8800.0, state=active]

可以看到,由于传递的HashMap中没有id属性,故执行的SQL语句中不再包含”and id=?“,查询语句返回了所有state=ACTIVE的数据。得到了多个Employee对象。

if标签中使用多个条件

如果想通过两个或多个条件搜索该怎么办呢?很简单,只要多加入一个或多个条件即可以。

使用多个and语句

EmployeeMapper.xml中添加入下select标签:

1
2
3
4
5
6
7
8
<!-- 测试多个if标签 -->
<select
id="selectEmployeeByStateIfLoginnamePassword"
resultType="domain.Employee">
select * from tb_employee where state='active'
<if test="loginname != null and password != null"> and loginname ={loginname} and password ={password}
</if>
</select>

mapper接口方法

EmployeeMapper.java接口中添加入下方法:

1
List<Employee> selectEmployeeByStateIfLoginnamePassword(HashMap<String, Object> params);

测试类

/MyDynamicSQLTest/src/test/DoubleIfTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package test;
import java.util.HashMap;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import domain.Employee;
import fractory.SqlSessionFratoryTools;
import mapper.EmployeeMapper;
public class DoubleIfTest {
public static void main(String[] args)
{
SqlSession sqlSession = null;
// 1.获取SqlSession实例
sqlSession = SqlSessionFratoryTools.getSqlSession();
// 2.获取mapper接口的代理对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 3.创建参数列表
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("loginname", "xiaoming");
params.put("password", "xiaoming");
List<Employee> employees = employeeMapper.selectEmployeeByStateIfLoginnamePassword(params);
employees.forEach(employee -> System.out.println(" " + employee));
}
}

运行结果

运行测试类DoubleIfTest,控制台显示效果如下:

DEBUG [main] ==>  Preparing: select * from tb_employee where state='active' and loginname = ? and password = ? 
DEBUG [main] ==> Parameters: xiaoming(String), xiaoming(String)
DEBUG [main] <==      Total: 1
    Employee [id=1, loginname=xiaoming, password=xiaoming, name=小明, sex=男, age=19, phone=123456789123, sal=9800.0, state=active]

可以看到,执行的SQL语句中因为传入了loginnamepassword属性,故SQL语句中包含了”and loginname=? and password=?“,查询返回的Emplyee对象就是loginnamexiaoming,并且passwordxiaoming的对象。