文章目录
- 1.单表查询
- 1.1返回所有的表记录
- 1.2根据id查找结果
- 1.3根据名字查找结果
- 2.单表修改
- 2.1修改密码
- 3.单表删除
- 3.1根据id删除信息
- 4.单表增加(根据业务情况返回)
- 4.1添加返回影响的行数
- 4.2添加返回影响行数和id
- 5.多表查询(多)
- 一对一
- 多对多
- 参数占位符#{}和${}区别:
- like查询
- sql注入
说明:
1.这里写完interface和xml中的内容,使用数据库和单元测试进行了检验
2. Mybatis拿参数的方式和SpringBoot一样,最通用的就是在加个@Param注解
对应的sql中的xml就需要用${ord}或者#{ord}的方式获取
3.如果传对象,一般不用再要@Param注解,如UserEntity user,这个时候xml写法:
直接写对象的名字,直接写属性名(框架帮助我们自动完成了映射)
如
4.多表删除和多表增加都可以在Serive层进行任务编排,一个一个属性的注入,对应的完成任务,所以mapper这一层只需要考虑单表的增删改查和多表的联合查询问题
5.数据库如果使用拼接,java这边使用大小驼峰的话,spring框架可以自动映射
1.单表查询
1.1返回所有的表记录
1)在UserMapper中建立对应的接口
package com.example.demo.mapper;
import com.example.demo.entity.UserEntity;
//名字是历史原因(以前的类可能还是ibatis这个名)
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
//mapper层就是数据持久层,叫做repostories也行
@Mapper//表明是组件之一
public interface UserMapper {
//单表查询
List<UserEntity> getAll();
}
2)在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">
<!-- 1.namespace:要实现的类包名+接口名-->
<!-- 2.id:实现的对应接口里的方法名-->
<!--实现什么方法,返回值-->
<select id="getAll" resultType="com.example.demo.entity.UserEntity">
select * from userinfo
<!-- 不用写分号-->
</select>
</mapper>
3)返回UserMapper中,点中方法,右键生成测试方法:
4)补充测试方法
- 为类加上@SpringBootTest注解
- 采用DI的方式注入UserMapper对象
package com.example.demo.mapper;
import com.example.demo.entity.UserEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest//表示当前单元测试是运行在Spring Boot环境下的
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void getAll() {
List<UserEntity> list=userMapper.getAll();
System.out.println(list);
}
}
在配置完对应的配置项,我们可以看到下边的测试结果:
下边是数据库中的验证结果:
1.2根据id查找结果
1)mapper中添加方法声明
UserEntity getUserById(@Param("id") Integer id);
2)xml文件中添加方法实现:
<select id="getUserById" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where id=#{id}
</select>
3)生成、补充、运行测试代码,去数据库进行验证
在配置完对应的配置项,我们可以看到下边的测试结果:
下边是数据库中对应的验证结果:
1.3根据名字查找结果
1)mapper中添加方法声明
UserEntity getUserByUsername(@Param("username") String username);
2)xml文件中添加方法实现:
<select id="getUserByUsername" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username=#{username}
</select>
3)生成、补充、运行测试代码,去数据库进行验证
@Test
void getUserByUsername() {
// 直接替换没有单引号
UserEntity user=userMapper.getUserByUsername("zhangsan");
System.out.println(user);
}
在配置完对应的配置项,我们可以看到下边的测试结果:
下边是数据库中对应的验证结果:
2.单表修改
这里使用的标签是update,这里只有一个id属性名,没有namespace其他的,至于resultType,因为方法那里默认就是返回int类型,所以可以省略不写。
一般来说修改用户名和密码是比较高频的操作,所以我们这里以修改密码为例。
2.1修改密码
代码实现步骤:
mapper中写接口方法–》xml中写对应sq语句—>添加对应的测试方法,进行校验–》数据库校验(略)
//修改密码
int updatePassword(@Param("id")Integer id,
@Param("password")String password,
@Param("newPassword")String newPassword);
<update id="updatePassword">
<!-- 默认返回值就是int-->
<!-- select和update标签的区别-->
update userinfo set password=#{newPassword}
where id=#{id} and password=#{password}
</update>
//默认污染,但是可以设置成不污染的
@Transactional//不会污染了==》会进行事务回滚
@Test
void updatePassword() {
int result=userMapper.updatePassword(1,"admin","abcdef");
System.out.println("修改"+result);
}
记得加@Transactional注解,不污染数据库,他可以加载方法上,也可以加载类上。
3.单表删除
这里使用的delete标签,只需要设置一个参数id,和update一样,默认返回受影响的行数。
3.1根据id删除信息
//删除用户
int delById(@Param("id")Integer id);
<delete id="delById">
delete from userinfo where id=#{id}
</delete>
@Transactional
@Test
void delById() {
int result=userMapper.delById(1);
System.out.println("删除"+result);
}
4.单表增加(根据业务情况返回)
这里使用的是insert标签,和前边两个标签一样,默认返回受影响的行数。
4.1添加返回影响的行数
以登录场景为例,推荐传递对象而不是传递两个参数,方便后续维护,否则会影响整个调用链。
//添加用户
int addUser(UserEntity user);
<insert id="addUser">
insert into userinfo(username,password) values(#{username0},#{password})
<insert>
@Transactional
@Test
void addUser() {
UserEntity user = new UserEntity();
user.setUsername("zhangsan");
// user.setPassword("123456");
int result = userMapper.addUser(user);
System.out.println("添加:" + result);
}
如果一批用户需要插入,看后边的动态sql,使用list
4.2添加返回影响行数和id
需要使用insert标签中userGeneratedKeys和keyProperty属性,第一个是是否生成自增主键,设置成true,第二个是将返回的主键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.set("123456");
int result = userMapper.addUserGetId(user);
System.out.println("添加结果:" + result);
System.out.println("ID:" + user.getId());
}
5.多表查询(多)
一对一
使用最多的就是left join或者right join,将查询后的结果重命名
一般实体类和数据库表是一致的,如果还要其他的,需要创建扩展类,然后继承上一层类。
@Data
public class ArticleInfoVO extends ArticleInfo {
private String username;
@Override
public String toString() {
return "ArticleInfoVO{" +
"username='" + username + '\'' +
"} " + super.toString();
}
//通过看字节码知道
}
以查询用户指定文章详情为例
// 查询文章详情
ArticleInfoVO getDetail(@Param("id") Integer id);
<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>
@Test
void getDetail() {
ArticleInfoVO articleInfoVO = articleMapper.getDetail(1);
System.out.println(articleInfoVO);
System.out.println("title:" + articleInfoVO.getTitle());
}
多对多
上边一个文章 我们规定只能一个作者,但是一个作者可以有多个文章,需要进行多对多的表查询
主表应该是文章,返回的结果应该是一个文章的列表。
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 u.id=a.uid
where a.uid=#{uid}
</select>
@Test
void getListByUid() {
Integer uid = 1;
List<ArticleInfoVO> list = articleMapper.getListByUid(uid);
//stream(语法糖)使用并行方式打印,顺序不确定(不需要自己开线程池),可以通过字节码看
list.stream().parallel().forEach(System.out::println);
// 使用串行方式
list.stream().forEach(System.out::println);
}
参数占位符#{}和${}区别:
#{}:原理是预编译处理。MyBatis处理#{}会将sql中的#{}替换成?号,使用Preparement的set方法赋值。
:原理是字符串直接替换。
M
y
B
a
t
i
s
处理
{}:原理是字符串直接替换。MyBatis处理
:原理是字符串直接替换。MyBatis处理{}会将它替换成变量的值。
共同点:都可以正确处理数值型参数。
#{}优缺点:优点可以有效防止SQL注入,提高系统安全性;缺点就是当java代码中参数名和数据库关键字相同时,会报错,那个时候考虑sql内置的concat方法。
例如:select * from userinfo order by id desc(比如tb或者pdd需要按照什么样的规则进行有限展示商品asc还是desc),此时就需要使用$
${}:优点可以解决关键字与sql语句关键字相同的问题,缺点解决非数据类型数据比较麻烦,有安全问题,通常是登录场景,有sql注入问题。
但是即使是上边的场景也是需要参数可以被枚举的,是可控的,不过不建议用这种方法,最好在Controller层Service层就能进行确定,做好了服务编排,提高安全性
结论:用于查询的字段,尽量使用#{}预编译处理的方式,尤其是的时候非数据类型的,否则不仅可能会有sql注入的问题,还有sql语句中缺少对应的引号。
like查询
当参数为非数据类型时,like使用#{}会报错,因为#{}实现原理是预编译,
最终这部分就是’%‘username’’%’
而我们又不想要sql注入,这里的解决方案就是使用concat方法:
<select id="getListByName" resultType="com.example.demo.entity.UserEntity">
select * from userinfo where username like concat('%',#{username},'%')
</select>
@Test
void getListByName() {
String username = "zhang";
List<UserEntity> list = userMapper.getListByName(username);
list.stream().forEach(System.out::println);
}
sql注入
使用了不符合要求的代码查到了本不该查到的代码。最典型的便是’or 1= 1’