MyBatis 查询数据库

news2024/11/24 15:35:20

✏️作者:银河罐头
📋系列专栏: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.

image-20230527194132009

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

image-20230527194800476

报错原因是还没有说明要连接的数据库 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

<?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.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

再次运行单元测试,

image-20230528213046555

确实是直接替换。

2.#{paramName} 占位符模式

UserMapper.xml :

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

再次启动单元测试:

image-20230529120037766

预执行

可以排除 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);
    }
}

image-20230529121339832

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

select * from userinfo where username = ${username}

image-20230529133612835

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

image-20230529133853409

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

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

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

image-20230529134441568

单元测试通过了。

==这是 ${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);
}

image-20230529140910360

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

image-20230529140749536

sql 注入问题

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

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

image-20230529141305866

在 UserMapper.xml 中:

把 ${} 改成 #{}

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

image-20230529181222461

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

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

image-20230529182500259

所以 表里只有一条数据时,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());
}

image-20230529184031431

单元测试通过。

把 $ 换成 #:

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

再次运行 单元测试:
image-20230529184945902

#{} 把 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);
}

image-20230529211150288

执行单元测试,报错!!

原因是,#{} 进行占位符替换会把 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>

image-20230529212227233

单元测试通过。

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

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

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

返回字典映射:resultMap

resultMap 使⽤场景:

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

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

image-20230530110209102

image-20230530110250465

举例:

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

private String pwd;

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

image-20230530104313654

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>

image-20230530105633674

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

image-20230530105832982

  • 如果我不想使用 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>

image-20230530110818830

多表查询

⼀对⼀的表映射

一篇文章对应一个作者。

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

image-20230530112044123

显示文章详情页时,要显示作者姓名,但是文章表里只有 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);
}
<?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.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);
    }
}

image-20230530131639328

没有打印父类 ArticleInfo 的属性。

image-20230530131940069

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

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

image-20230530132514046

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

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

image-20230530132820811

⼀对多的表映射

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

image-20230530135813846

@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);//多线程
}

image-20230530141044209

单表增、删、改操作

修改

//修改密码
int updatePassword(@Param("id")Integer id,
                   @Param("password") String password,
                   @Param("newPassword") String newPassword);
<update id="updatePassword">
<!-- select 有 2 个参数, 而 update 只有 id 这一个参数-->
        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);
}

image-20230529192949663

单元测试通过。

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 回滚操作,所以就没有污染数据库。

image-20230529194218600

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);
}

image-20230529195508426

添加

  • 返回影响行数
//添加用户
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);
}

image-20230529201108774

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">
                          <!-- 是否要生成 key 属性-->
    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());
}

image-20230529203054862

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);
}

image-20230530150522558

传 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);
}

image-20230530150832737

注意 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);
}

image-20230530153623949

image-20230530153717631

传 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);
    }

image-20230530160051910

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 标签

image-20230531100356645

根据 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());
}

image-20230531094438453

传 id = 1:

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

image-20230531094724345

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());
}

image-20230531095953365

传 title like “s”:

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

image-20230531100259627

以上 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());
}

image-20230531172508368

果然报错!

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);
}

image-20230531175107652

image-20230531175140794

但是要求至少有一个参数不为 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);
}

image-20230531181524158

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605832.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【VBA】实现批量生成二维码

系列文章 【C#】单号生成器&#xff08;编号规则、固定字符、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成器&#xff08;开始日期、结束日期&#xff09; 本文链接&#xff1a;h…

Nginx - ​一个高性能、灵活可靠的开源Web服务器

Nginx是什么&#xff1f; Nginx是一个高性能的HTTP和反向代理web服务器&#xff0c;同时也提供了IMAP/POP3/SMTP服务。Nginx是由伊戈尔赛索耶夫为俄罗斯访问量第二的Rambler.ru站点&#xff08;俄文&#xff1a;Рамблер&#xff09;开发的&#xff0c;第一个公开版本0.1…

idea使用native-image打包springboot项目

native-image简介 native-image 是一个用于将 Java 程序编译为本地可执行文件的工具。它是 GraalVM 的一部分&#xff0c;GraalVM 是一个高性能的通用虚拟机&#xff0c;支持多种语言。 使用步骤 下载GraalVM 安装 GraalVM&#xff1a;首先&#xff0c;你需要安装 GraalVM。…

20230603-周六随笔

周六闲来无事&#xff0c;给新电脑装下开发环境&#xff0c;记录一下遇到的问题 git下载代码报错 报错1&#xff1a;schannel: SEC_E_UNTRUSTED_ROOT (0x80090325)解决方法&#xff1a;执行git config --system http.sslbackend openssl命令 报错2&#xff1a;SSL certifica…

【Java 8 新特性】获取对象列表中的某个属性组成的列表

文章目录 获取对象列表中的某个属性组成的列表1、用法示例2、详细案例 附录&#xff1a;Java 8 Stream 基本用法1、map2、filter3、forEach4、limit5、sorted6、并行&#xff08;parallel&#xff09;程序7、Collectors8、统计 获取对象列表中的某个属性组成的列表 1、用法示例…

高完整性系统工程(十一):Fault Tolerant Design

目录 1. INTRODUCTION TO FAULT TOLERANCE 1.2 Definitions 1.3 Two Kinds of Faults 1.4 Hardware vs Software Faults 1.4.1 Failure Curve for Hardware 1.4.2 Hardware and Software Failures 1.5 Causes of Failures 1.6 3 Ways to Class Failures 1.6.1 Tempora…

【LLM】大模型值得探索的十个研究方向

note 基础理论&#xff1a;大模型的基础理论是什么&#xff1f; 网络架构&#xff1a;Transformer是终极框架吗&#xff1f; 高效计算&#xff1a;如何使大模型更加高效&#xff1f; 高效适配&#xff1a;大模型如何适配到下游任务&#xff1f; 可控生成&#xff1a;如何实…

ChatGPT有关的模块知多少?

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 ChatGPT有关的模块知多少&#xff1f; &#x1f9ca;摘要&#x1f9ca;ChatGPT 开发库清单 &#x1f9ca;摘要 本文介绍了基于OpenAI ChatGPT 的API 开发的python 模块库。【原创&am…

STM32cubemx定时外部模式测量10M以上频率

STM32cubemx定时外部模式测量10M以上频率 本文讲解利用定时器的外部时钟功能&#xff0c;巧妙测量高频外部信号频率。范围可以到高达30M以上。 所需工具&#xff1a; 开发板:STM32F103RCT6STM32CubeMXIDE: Keil-MDK 文章目录 STM32cubemx定时外部模式测量10M以上频率原理讲解…

【Java技术专题】「Guava开发指南」手把手教你如何进行使用Guava工具箱进行开发系统实战指南(Optional篇)

Guava相关的介绍 Guava工程包含了许多被Google的Java项目广泛依赖的核心库。我们希望通过本文档为Guava中最流行和最强大的功能提供更具可读性和解释性的说明。 本教程是中级教程&#xff0c;适合 Guava 中级开发者的进阶学习。 学习Guava前的准备工作 学习目标和计划&#xf…

Python学习笔记 - 探索集合Set

尊敬的各位&#xff0c;我是Mr.数据杨&#xff0c;非常高兴和你们一起走进Python的世界。今天&#xff0c;让我们一起探讨Python中神奇的“集合”。你们是否读过《三国演义》&#xff1f;你们应该记得那场赫赫有名的“桃园三结义”吧&#xff1f;那让我们以此为例&#xff0c;来…

Windows动态链接库的生成和使用

工程需要&#xff0c;最近在编一组Windows上的动态链接库给Python调用。之前做过Linux下C动态库的编译&#xff0c;并提供给Python调用&#xff0c;Windows下的编译跟Linux还是有些差距&#xff0c;因此花了一点时间跑通&#xff0c;在这里记录一下。 为了完整对比&#xff0c…

Hive on Spark环境搭建

Hive 引擎简介 Hive 引擎包括&#xff1a;默认 MR、tez、spark 最底层的引擎就是MR &#xff08;Mapreduce&#xff09;无需配置&#xff0c;Hive运行自带 Hive on Spark&#xff1a;Hive 既作为存储元数据又负责 SQL 的解析优化&#xff0c;语法是 HQL 语法&#xff0c;执行…

(2.54mm)TSM-120-04-S-DV-P-TR方形接线柱针脚、ADRF5545ABCPZN(通用)射频前端 SPDT

TSM-120-04-S-DV-P-TR (2.54mm) 表面安装.025"方形接线柱针脚是板对板连接器&#xff0c;有单排、双排或三排方形接线柱端子可供选择&#xff0c;带直通、直角或混合技术引脚。这些高度可靠的坚固针脚有垂直和水平两种方向&#xff0c;在混合气流 (MFG) 环境中可使用10年。…

【C语言实现简易ATM】上个C语言程序设计课,我成产品经理了?

博主简介&#xff1a;Hello大家好呀&#xff0c;我是陈童学&#xff0c;一个与你一样正在慢慢前行的人。 博主主页&#xff1a;陈童学哦 所属专栏&#xff1a;C语言程序设计实验项目 如果本文对你有所帮助的话&#xff0c;还希望可以点赞&#x1f44d;收藏&#x1f4c2;支持一下…

2023年京东618满300减50是全品类吗?满减叠卷怎么用?

2023年京东618满300减50是全品类吗?满减叠卷怎么用? 京东平台上有着比较多的卖家开店&#xff0c;在对店铺进行运营的过程中&#xff0c;很多卖家都会参与平台的一些活动&#xff0c;这样能够有效的将产品推广出去&#xff0c;对于618大促活动也是属于其中活动之一&#xff0…

代码随想录第52天

1.最长递增子序列 接下来&#xff0c;我们依然用动规五部曲来详细分析一波&#xff1a; dp[i]的定义 本题中&#xff0c;正确定义dp数组的含义十分重要。 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 为什么一定表示 “以nums[i]结尾的最长递增子序” &…

软考A计划-电子商务设计师-专业英语

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

高压侧电流检测电路及仿真

高压侧电流检测电路仿真 电路图如下 主要设计思想&#xff0c;通过两组电阻将高压侧的共模电压降到运放&#xff08;此处也可以使用单电源运放&#xff09;的工作电压范围内。然后在进行二次放大。主要放大倍数取决于第二级放大侧电阻比值。因此如果需要减小功耗&#xff0c;可…

JAVA数组基础

目录 一、使用方式 1-动态初始化 ①先声明数组 ② 创建数组 ③分配方式 二、使用方式 2-静态初始化&#xff08;直接在声明的同时初始化{ } &#xff09; 三、数组使用注意事项和细节 四、数组两种初始化方式都是将内存空间分配到堆上面的 一、使用方式 1-动态初始化 …