MyBatis 查询数据库

✏️作者:银河罐头
系列专栏:JavaEE

“种一棵树最好的时间是十年前,其次是现在”

目录

  • MyBatis 是什么?
  • 第⼀个MyBatis查询
    • 创建数据库和表
    • 添加MyBatis框架支持
    • 设置 MyBatis 配置信息
    • 添加业务代码
  • 查询操作
    • 单表查询
      • ${paramName} 和 #{paramName}
      • sql 注入问题
      • like 查询
      • 返回字典映射:resultMap
    • 多表查询
      • ⼀对⼀的表映射
      • ⼀对多的表映射
  • 单表增、删、改操作
    • 修改
    • 删除
    • 添加
  • 复杂情况:动态SQL使用
    • if 标签
    • trim 标签
    • where 标签
    • set 标签
    • foreach 标签

MyBatis 是什么?

简单来说 MyBatis 是更简单完成程序和数据库交互的⼯具,也就是更简单的操作和读取数据库⼯具。

MyBatis 基于 JDBC.

MyBatis 也是⼀个 ORM 框架,ORM(Object Relational Mapping),即对象关系映射。

数据库表(table)–> 类(class)

记录(record,⾏数据)–> 对象(object)

字段(field) --> 对象的属性(attribute)

⼀般的 ORM 框架,会将数据库模型的每张表都映射为⼀个 Java 类。 也就是说使⽤ MyBatis 可以像操作对象⼀样来操作数据库中的表。

第⼀个MyBatis查询

创建数据库和表

使⽤ MyBatis 的⽅式来读取⽤户表中的所有⽤户,

具体 sql 语句如下:

-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;

-- 使用数据数据
use mycnblog;

-- 创建表[用户表]
drop table if exists  userinfo;
create table userinfo(
    id int primary key auto_increment,
    username varchar(100) not null,
    password varchar(32) not null,
    photo varchar(500) default '',
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `state` int default 1
) default charset 'utf8mb4';

-- 创建文章表
drop table if exists  articleinfo;
create table articleinfo(
    id int primary key auto_increment,
    title varchar(100) not null,
    content text not null,
    createtime timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    uid int not null,
    rcount int not null default 1,
    `state` int default 1
)default charset 'utf8mb4';

-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
  	vid int primary key,
  	`title` varchar(250),
  	`url` varchar(1000),
		createtime timestamp default current_timestamp,
		updatetime timestamp default current_timestamp,
  	uid int
)default charset 'utf8mb4';

-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES 
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);

-- 文章添加测试数据
insert into articleinfo(title,content,uid)
    values('Java','Java正文',1);
    
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
mysql> select database();//查看当前指令在操纵哪个数据库
+------------+
| database() |
+------------+
| mycnblog   |
+------------+
1 row in set (0.00 sec)

添加MyBatis框架支持

新建项目多添加 2 个依赖: MyBatis Framework 和 MySQL Driver.

MyBatis 查询数据库_第1张图片

第一次创建好了 MyBatis 项目之后,启动会报错。

MyBatis 查询数据库_第2张图片

报错原因是还没有说明要连接的数据库 url 是?用户名?密码?。

设置 MyBatis 配置信息

1)设置数据库连接的相关信息:

application.properties 配置文件里设置:

spring.datasource.url= jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

2)MyBatis xml 文件的保存路径和 xml 的命名模式

#设置 MyBatis
mybatis.mapper-locations=classpath:/mybatis/*Mapper.xml

添加业务代码

1.interface: 让其他层可以注入使用的接口

2.xml: 具体实现 sql ( interface 的"实现")

1)添加实体类

@Data
public class UserEntity {
    private Integer id;
    private String username;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}

2)添加 mapper 接口

@Mapper
public interface UserMapper {
    List<UserEntity> getAll();
}

3)添加 UserMapper.xml


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="getAll" resultType="com.example.demo.mapper.UserMapper">
        select * from userinfo
    select>
mapper>

4)添加 service

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    public List<UserEntity> getAll(){
        return userMapper.getAll();
    }
}

5)添加 controller

@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/getall")
    public List<UserEntity> getAll(){
        return userService.getAll();
    }
}

image-20230527214152600

再插入一条数据测试:

mysql> INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
    -> (2, 'zhangsan', 'zhangsan', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
Query OK, 1 row affected (0.00 sec)

image-20230527214526868

查询操作

单表查询

实现⼀下根据⽤户 id 查询⽤户信息的功能。

@Mapper
public interface UserMapper {
     //根据 id 查询某个用户信息
     UserEntity getUserById(@Param("id") Integer id);
}
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where id = ${id};
select>

${}:字符直接替换。是MyBatis 在处理 ${} 时,就是把 ${} 替换成变量的值。

写完这个 getUserById() 这个方法,我想验证这个方法的功能,可以像之前一样在 浏览器地址栏输入 url 返回数据验证(Controller -> Service -> Mapper 层层调用)。除此之外,还可以通过更加简单的"单元测试"来验证功能。

关于单元测试如何使用可以看我另一篇博客。

链接:

  • 如果 @Param 里面的参数和形参不一样,能成功查询到吗?

image-20230528211252923

通过单元测试验证,结果报错。

image-20230528211425316

select * from userinfo where id = ${uid};

只需要把 UserMapper.xml 里 ${} 里的 “id” 改成 “uid” 就行了。

${paramName} 和 #{paramName}

  • MyBatis 获取动态参数有 2 种方式。
  1. ${paramName} 直接替换

验证:打印 MyBatis 执行的 sql

#打印 MyBatis 执行的 sql
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#因为打印 MyBatis 执行的 sql 日志级别是 debug,而默认级别是 info,所以要修改日志的默认级别为 debug
logging.level.com.example.demo=debug

再次运行单元测试,

MyBatis 查询数据库_第3张图片

确实是直接替换。

2.#{paramName} 占位符模式

UserMapper.xml :

<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where id = #{uid}
select>

再次启动单元测试:

MyBatis 查询数据库_第4张图片

预执行

可以排除 sql 注入的问题。

这里传入的 是 Integer 类型的数据。还看不出 ${paramName} 和 #{paramName} 的区别。

  • 下面通过传入 String 类型数据来观察这二者的差别。
@Mapper
public interface UserMapper {
     //根据 username 查询某个用户信息
     UserEntity getUserByUsername(@Param("username") String username);
}

UserMapper.xml :

<select id="getUserByUsername" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username = #{username}
select>
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void getUserByUsername() {
        UserEntity user = userMapper.getUserByUsername("zhangsan");
        System.out.println(user);
    }
}

MyBatis 查询数据库_第5张图片

把 # 改成 $ ,再次运行单元测试,会发生什么?

select * from userinfo where username = ${username}

MyBatis 查询数据库_第6张图片

报错了,“没有找到 zhangsan 这一列”。

image-20230529133853409

观察发现,idea 的 报错信息和 MySQL 报错信息是一样的。

${paramName} 直接替换的方式,就直接换成了 zhangsan , 没有加 ’ ’ , 所以报错。如果传入参数是 int 类型就不会有这种问题。

  • 那我手动给它再最外面加上 ’ ',能运行成功吗?
select * from userinfo where username = '${username}'

MyBatis 查询数据库_第7张图片

单元测试通过了。

==这是 ${paramName} 和 #{paramName} 的第一个区别:

${paramName} 是直接替换,而#{paramName} 是 占位符 预处理的。

第二个区别是 sql 注入的问题。==

sql 注入:用了一个不真实的用户名和密码查询到了数据。

@Mapper
public interface UserMapper {
     //登录方法
     UserEntity login(UserEntity user);//对象传参
}
<select id="login" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username = '${username}' and password = '${password}'
select>
@Test
void login() {
    String username = "admin";
    String password = "admin";
    UserEntity inputUser = new UserEntity();
    inputUser.setUsername(username);
    inputUser.setPassword(password);
    UserEntity user = userMapper.login(inputUser);
    System.out.println(user);
}

MyBatis 查询数据库_第8张图片

把 password 改成 “admin2”, 再次运行,查到的是 空 user.

MyBatis 查询数据库_第9张图片

sql 注入问题

  • ${} sql 注入问题
String password = "' or 1='1";

把 password 改成这个,再次运行:

MyBatis 查询数据库_第10张图片

在 UserMapper.xml 中:

把 ${} 改成 #{}

select * from userinfo where username=#{username} and password=#{password}

MyBatis 查询数据库_第11张图片

${} 把 “’ or 1='1” 理解成是 sql 指令,不安全有 sql 注入问题。

#{} 是预执行占位符,不管是多么奇怪的像 “’ or 1='1” 这样都理解成是 字符串。

MyBatis 查询数据库_第12张图片

所以 表里只有一条数据时,select * from userinfo where username = ‘ u s e r n a m e ′ a n d p a s s w o r d = ′ {username}' and password = ' usernameandpassword={password}’ 可以查询到(sql 注入)。

这样看来,那么 ${} 就一无是处了吗?

不是!

  • 以排序举例:
List<UserEntity> getAllByIdOrder(@Param("ord") String ord);
<select id="getAllByIdOrder" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo order by id ${ord};
select>
@Test
void getAllByIdOrder() {
    List<UserEntity> list = userMapper.getAllByIdOrder("desc");
    System.out.println(list.size());
}

MyBatis 查询数据库_第13张图片

单元测试通过。

把 $ 换成 #:

select * from userinfo order by id #{ord};

再次运行 单元测试:
MyBatis 查询数据库_第14张图片

#{} 把 sql 语句解析成:

select * from userinfo order by id 'desc';

所以报错!

使⽤ ${sort} 可以实现排序查询,⽽使⽤ #{sort} 就不能实现排序查询了,因为当使⽤ #{sort} 查询时, 如果传递的值为 String 则会加单引号,就会导致 sql 错误。

小结:因为 ${} 存在 sql 注入的 问题,所以要慎用,如果是排序这种特殊的场景适合用 ${}, 但是要保证你传入的参数是可以被枚举的,比如{asc, desc}.

like 查询

//根据用户名进行模糊查询
List<UserEntity> getListByName(@Param("username") String username);
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username like '%#{username}%'
select>
@Test
void getListByName() {
    String username = "zhang";
    List<UserEntity> list = userMapper.getListByName(username);
    list.stream().forEach(System.out::println);
}

MyBatis 查询数据库_第15张图片

执行单元测试,报错!!

原因是,#{} 进行占位符替换会把 zhangsan 这个 字符串最外层加引号 ’ ’

select * from userinfo where name like '%#{username}%'
#{}=>
select * from userinfo where name like '%'zhangsan'%'

可以考虑使⽤ mysql 的内置函数 concat() 来处理。

mysql> select concat('%','zhang','%');
+-------------------------+
| concat('%','zhang','%') |
+-------------------------+
| %zhang%                 |
+-------------------------+
1 row in set (0.00 sec)

mysql> select concat('%','zhang','%','xxx','ddd');
+-------------------------------------+
| concat('%','zhang','%','xxx','ddd') |
+-------------------------------------+
| %zhang%xxxddd                       |
+-------------------------------------+
1 row in set (0.00 sec)

修改 UserMapper.xml :

<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
    select * from userinfo where username like concat('%',#{username},'%')
select>

MyBatis 查询数据库_第16张图片

单元测试通过。

“%张” 和 “%张%” ,索引会失效;

“张%”,索引不会失效。

总结:%开头的索引就会失效。

返回字典映射:resultMap

resultMap 使⽤场景:

  • 字段名称和程序中的属性名不同的情况,可使⽤ resultMap 配置映射;

  • ⼀对⼀和⼀对多关系可以使⽤ resultMap 映射并查询数据。

MyBatis 查询数据库_第17张图片

MyBatis 查询数据库_第18张图片

举例:

如果我 实体类里写的是 pwd 字段而不是 数据库里的 password,

private String pwd;

此时 再次执行 getListByName() 单元测试

MyBatis 查询数据库_第19张图片

pwd = null, 数据库表里已经查到 一条数据,但是和 实例类 的 pwd 字段没有成功映射。

<resultMap id="BaseMap" type="com.example.demo.entity.UserEntity">
    <id property="id" column="id">id>
    <result property="username" column="username">result>
    <result property="pwd" column="password">result>
    <result property="createtime" column="createtime">result>
    <result property="updatetime" column="updatetime">result>
resultMap>

MyBatis 查询数据库_第20张图片

再次运行 getListByName() 单元测试:

MyBatis 查询数据库_第21张图片

  • 如果我不想使用 resultMap 这种方式,还有别的办法实现 password -> pwd 的映射吗?

查询数据的时候 把 password 重命名为 pwd.

<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
    select id, username, password as pwd from userinfo where username like concat('%',#{username},'%')
select>

MyBatis 查询数据库_第22张图片

多表查询

⼀对⼀的表映射

一篇文章对应一个作者。

举例:实现 文章表详情页的查询。

MyBatis 查询数据库_第23张图片

显示文章详情页时,要显示作者姓名,但是文章表里只有 uid,没有 username 这个字段,而userinfo 表里才有 username 这个字段。

所以要通过多表联查。

VO(View Object):视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。 VO,就是一切给 View 提供数据的对象。

@Data
public class ArticleInfo {
    private int id;
    private String title;
    private String content;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int uid;
    private int rcount;
    private int state;
}
@Data
public class ArticleInfoVO extends ArticleInfo {
    private String username;
}
@Mapper
public interface ArticleMapper {
    //查询文章详情
    ArticleInfoVO getDetail(@Param("id") Integer id);
}

DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ArticleMapper">
    <select id="getDetail" resultType="com.example.demo.entity.vo.ArticleInfoVO">
        select a.*, u.username from articleinfo a
        left join userinfo u on u.id = a.uid
        where a.id = #{id}
    select>
mapper>
@SpringBootTest
class ArticleMapperTest {

    @Autowired
    private ArticleMapper articleMapper;

    @Test
    void getDetail() {
        ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
        System.out.println(articleInfoVO);
    }
}

MyBatis 查询数据库_第24张图片

没有打印父类 ArticleInfo 的属性。

MyBatis 查询数据库_第25张图片

查看 class 文件,发现是注解 lombok.Data 的问题。

我们可以 自己重写 toString() 方法把父类 ArticleInfo 的属性也给打印了。

MyBatis 查询数据库_第26张图片

@Data
public class ArticleInfoVO extends ArticleInfo {
    private String username;
    @Override
    public String toString() {
        return "ArticleInfoVO{" +
                "username='" + username + '\'' +
                "} " + super.toString();
    }
}

自己写了 toString() 之后,lombok 写的 toString() 就无效了。

MyBatis 查询数据库_第27张图片

⼀对多的表映射

⼀个⽤户对应多篇⽂章案例:

MyBatis 查询数据库_第28张图片

@Mapper
public interface ArticleMapper {

    List<ArticleInfoVO> getListByUid(@Param("uid") Integer uid);
}
<select id="getListByUid" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select a.*, u.username from articleinfo a
    left join userinfo u on a.uid = u.id
    where a.uid = #{uid}
select>
@Test
void getListByUid() {
    Integer uid = 1;
    List<ArticleInfoVO> list = articleMapper.getListByUid(uid);
    list.stream().parallel().forEach(System.out::println);//多线程
}

MyBatis 查询数据库_第29张图片

单表增、删、改操作

修改

//修改密码
int updatePassword(@Param("id")Integer id,
                   @Param("password") String password,
                   @Param("newPassword") String newPassword);
<update id="updatePassword">

        update userinfo set password = #{newPassword}
        where id = #{id} and password = #{password}
update>
@Test
void updatePassword() {
    int result = userMapper.updatePassword(1,"admin","123456");
    System.out.println("修改: " + result);
}

MyBatis 查询数据库_第30张图片

单元测试通过。

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | admin    |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

数据库里也确实把 password 修改了。

之前说过 单元测试有一个 优点是 “不污染数据库”,不过这里为了验证修改功能污染了数据库!

可以通过加注解 @Transactional 的方式来达到"不污染数据库"的效果。

@Transactional//事务
@Test
void updatePassword() {
     int result = userMapper.updatePassword(1,"123456","abcdefg");
     System.out.println("修改: " + result);
}

执行单元测试之初会开启一个事务,执行完毕会执行 rollback 回滚操作,所以就没有污染数据库。

MyBatis 查询数据库_第31张图片

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
1 row in set (0.00 sec)

能够成功修改密码,同时数据库密码没有被修改。

删除

//删除用户
int deleteById(@Param("id") Integer id);
<delete id="deleteById">
    delete from userinfo where id = #{id}
delete>
@Transactional
@Test
void deleteById() {
    int result = userMapper.deleteById(1);
    System.out.println("删除: " + result);
}

MyBatis 查询数据库_第32张图片

添加

  • 返回影响行数
//添加用户
int addUser(UserEntity user);
<insert id="addUser">
    insert into userinfo(username,password) values(#{username}, #{password});
insert>
@Test
void addUser() {
    UserEntity user = new UserEntity();
    user.setUsername("zhangsan");
    user.setPassword("123456");
    int result = userMapper.addUser(user);
    System.out.println("添加: " + result);
}

MyBatis 查询数据库_第33张图片

mysql>  select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
2 rows in set (0.00 sec)

这样实现了能看到影响的行数。

如果我想要看到 新添加 的用户的 id?

  • 返回影响行数和 id
int addUserGetId(UserEntity user);
<insert id="addUserGetId" useGeneratedKeys="true" keyProperty="id">
                          
    insert into userinfo(username,password) values(#{username}, #{password})
insert>
@Test
void addUserGetId() {
    UserEntity user = new UserEntity();
    user.setUsername("lisi");
    user.setPassword("123456");
    int result = userMapper.addUserGetId(user);
    System.out.println("添加结果: " + result);
    System.out.println("id: " + user.getId());
}

MyBatis 查询数据库_第34张图片

mysql>  select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
3 rows in set (0.00 sec)

复杂情况:动态SQL使用

if 标签

动态 sql 是Mybatis的强大特性之⼀,能够完成不同条件下不同的 sql 拼接。

 //`state` int(11) DEFAULT '1',
 mysql> insert into userinfo(username, password, state) values('wangwu','123456',null);
Query OK, 1 row affected (0.00 sec)
mysql>  insert into userinfo(username, password) values('wangwu2','123456');
Query OK, 1 row affected (0.00 sec)
mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 |  NULL |
|  5 | wangwu2  | 123456   |       | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
5 rows in set (0.00 sec)

如果在添加⽤户的时候有不确定的字段传⼊,程序应该 如何实现呢?

比如 userinfo 表里的 photo 字段,用户可能输入也可能不输入。

这个时候就需要使⽤动态标签来判断了. 如添加的时候性别 photo 为⾮必填字段.

@Mapper
public interface UserMapper {
     int addUser2(UserEntity user);
}
<insert id="addUser2">
    insert into userinfo(username,password
    <if test="photo != null and photo != ''">
        ,photo
    if>
    ) values(#{username}, #{pwd}
    <if test="photo != null and photo != ''">
        ,#{photo}
    if>
    )
insert>

传 username, password 2 个参数:

@Transactional
@Test
void addUser2() {
    String username = "liliu";
    String password = "123456";
    UserEntity user = new UserEntity();
    user.setUsername(username);
    user.setPwd(password);
    int result = userMapper.addUser2(user);
    System.out.println("添加: " + result);
}

MyBatis 查询数据库_第35张图片

传 username, password, photo 3 个参数:

@Transactional
@Test
void addUser2() {
    String username = "liliu";
    String password = "123456";
    UserEntity user = new UserEntity();
    user.setUsername(username);
    user.setPwd(password);
    user.setPhoto("cat.png");
    int result = userMapper.addUser2(user);
    System.out.println("添加: " + result);
}

MyBatis 查询数据库_第36张图片

注意 test 中的 photo,是传入对象中的属性,不是数据库字段。

  • 为了证明这一点,把 UserEntity 里的属性 photo 改成 img:
@Data
public class UserEntity {
    private Integer id;
    private String username;
    private String pwd;
    private String img;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private Integer state;
}
<insert id="addUser2">
    insert into userinfo(username,password
    <if test="img != null and img != ''">
        ,photo
    if>
    ) values(#{username}, #{pwd}
    <if test="img != null and img != ''">
        ,#{img}
    if>
    )
insert>
@Transactional
@Test
void addUser2() {
    String username = "liliu";
    String password = "123456";
    UserEntity user = new UserEntity();
    user.setUsername(username);
    user.setPwd(password);
    user.setImg("cat.png");
    int result = userMapper.addUser2(user);
    System.out.println("添加: " + result);
}

MyBatis 查询数据库_第37张图片

MyBatis 查询数据库_第38张图片

传 2 个参数和 传 3 个参数 都能通过单元测试。

trim 标签

如果所有字段都是⾮必填项,就考虑使⽤ 标签结合标签,对多个字段都采取动态⽣成的⽅式。

多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于 suffixOverrides 配置去掉最后⼀个 ,

int addUser3(UserEntity user);
<insert id="addUser3">
    insert into userinfo
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="username != null and username != ''">
            username,
        if>
        <if test="pwd != null and pwd != ''">
            password,
        if>
        <if test="img != null and img != ''">
            photo
        if>
    trim>
    <trim prefix="values(" suffix=")" suffixOverrides=",">
        <if test="username != null and username != ''">
            #{username},
        if>
        <if test="pwd != null and pwd != ''">
            #{pwd},
        if>
        <if test="img != null and img != ''">
            #{img},
        if>
    trim>
insert>
@Test
    void addUser3() {
        String username = "liliu";
        String password = "123456";
        UserEntity user = new UserEntity();
        user.setUsername(username);
        user.setPwd(password);
        int result = userMapper.addUser3(user);
        System.out.println("添加: " + result);
    }

MyBatis 查询数据库_第39张图片

mysql> select * from userinfo;
+----+----------+----------+-------+---------------------+---------------------+-------+
| id | username | password | photo | createtime          | updatetime          | state |
+----+----------+----------+-------+---------------------+---------------------+-------+
|  1 | admin    | 123456   |       | 2021-12-06 17:10:48 | 2021-12-06 17:10:48 |     1 |
|  2 | zhangsan | 123456   |       | 2023-05-29 20:10:14 | 2023-05-29 20:10:14 |     1 |
|  3 | lisi     | 123456   |       | 2023-05-29 20:27:53 | 2023-05-29 20:27:53 |     1 |
|  4 | wangwu   | 123456   |       | 2023-05-30 14:43:23 | 2023-05-30 14:43:23 |  NULL |
|  5 | wangwu2  | 123456   |       | 2023-05-30 14:44:30 | 2023-05-30 14:44:30 |     1 |
| 13 | liliu    | 123456   |       | 2023-05-30 15:57:37 | 2023-05-30 15:57:37 |     1 |
+----+----------+----------+-------+---------------------+---------------------+-------+
6 rows in set (0.00 sec)

(MySQL 5.7 版本) 事务回滚会导致自增主键 不连续

where 标签

MyBatis 查询数据库_第40张图片

根据 id 或 title 查询文章

List<ArticleInfoVO> getListByIdOrTitle(@Param("id") Integer id, @Param("title") String title);

通过 if 标签和 trim 标签这样写:

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    where
    <trim suffixOverrides="and">
        <if test="id != null and id > 0">
            id = #{id} and
        if>
        <if test="title != null and title != ''">
            title like concat('%',#{title},'%')
        if>
    trim>
select>

但是这样写的话,如果 id 和 title 2 个参数都不传的话,这个 sql 语句就会报错。

MyBatis 中多个非必传参数的解决方案:

1.可以加个条件"1=1"

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    where 1 = 1
    <trim prefixOverrides="and">
        <if test="id != null and id > 0">
            and id = #{id} 
        if>
        <if test="title != null and title != ''">
            and title like concat('%',#{title},'%')
        if>
    trim>
select>

2.trim prefix=“where”

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <trim prefix="where" suffixOverrides="and">
        <if test="id != null and id > 0">
            id = #{id} and
        if>
        <if test="title != null and title != ''">
            title like concat('%',#{title},'%')
        if>
    trim>
select>

trim 标签中有代码,才会有前缀 prefix 和后缀。

id 和 title 都不传:

@Test
void getListByIdOrTitle() {
    List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,null);
    System.out.println(list.size());
}

MyBatis 查询数据库_第41张图片

传 id = 1:

@Test
void getListByIdOrTitle() {
    List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(1,null);
    System.out.println(list.size());
}

MyBatis 查询数据库_第42张图片

trim 标签的这 2 种解决方案都可行,不过有点麻烦。

3.where 标签

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <where>
        <if test="id != null and id > 0">
            id = #{id}
        if>
        <if test="title != null and title != ''">
            and title like concat('%',#{title},'%')
        if>
    where>
select>

id 和 title 都不传:

@Test
void getListByIdOrTitle() {
    List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,null);
    System.out.println(list.size());
}

MyBatis 查询数据库_第43张图片

传 title like “s”:

@Test
void getListByIdOrTitle() {
    List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(null,"s");
    System.out.println(list.size());
}

MyBatis 查询数据库_第44张图片

以上 where 标签也可以使⽤ trim prefix = “where” prefixOverrides = "and"替换。

where 标签自动帮你去除掉最前面的 and 关键字,但是 where 标签不会帮你去除掉后面的 and 关键字。

<select id="getListByIdOrTitle" resultType="com.example.demo.entity.vo.ArticleInfoVO">
    select * from articleinfo
    <where>
        <if test="id != null and id > 0">
            id = #{id} and
        if>
        <if test="title != null and title != ''">
            title like concat('%',#{title},'%')
        if>
    where>
select>

只传 id 参数:

@Test
void getListByIdOrTitle() {
    List<ArticleInfoVO> list = articleMapper.getListByIdOrTitle(1,null);
    System.out.println(list.size());
}

MyBatis 查询数据库_第45张图片

果然报错!

set 标签

int updateById(UserEntity user);
<update id="updateById" parameterType="com.example.demo.entity.UserEntity">
    update userinfo
    <set>
        <if test="username">
            username = #{username},
        if>
        <if test="pwd">
            password = #{pwd},
        if>

    set>
    where id = #{id}
update>
@Test
void updateById() {
    String username = "zhaoliu";
    String pwd = "zhaoliu";
    UserEntity user = new UserEntity();
    user.setUsername(username);
    user.setPwd(pwd);
    user.setId(5);
    int result = userMapper.updateById(user);
    System.out.println(result);
}

MyBatis 查询数据库_第46张图片

MyBatis 查询数据库_第47张图片

但是要求至少有一个参数不为 null.

mysql> update userinfo where id = 1;
ERROR 1064 (42000): 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 'where id = 1' at line 1

如果所有参数都是 null, 就会报错.

foreach 标签

对集合进⾏遍历时可以使⽤该标签。

标签有如下属性:

collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象

item:遍历时的每⼀个对象

open:语句块开头的字符串

close:语句块结束的字符串

separator:每次遍历之间间隔的字符串

举例:根据多个文章 id 来删除文章。

int deleteByIds(List<Integer> ids);
<delete id="deleteByIds">
    delete from articleinfo where id in
    <foreach collection="list" item="item" open="(" close=")" separator=",">
        #{item}
    foreach>
delete>
@Transactional
@Test
void deleteByIds() {
    List<Integer> list = new ArrayList<>();
    Collections.addAll(list,1,2,3);
    int result = articleMapper.deleteByIds(list);
    System.out.println(result);
}

MyBatis 查询数据库_第48张图片

你可能感兴趣的:(JavaEE进阶,mybatis,java,数据库)