目录
1. 数据的查找 select
1.1 查询所有数据
1.2 通过 id 进行查找
2. 插入数据 insert
3. 修改数据 update
4. 删除数据 delete
5. $ 和 # 的区别
5.1 SQL 注入 用户登录
6. Spring Boot 打印 SQL 日志
7. order by 排序
8. like 查询
9. 通过页面返回数据
10. 总结
在上篇文章中我们介绍了 mybatis 的相关概念,通过 mybatis 对数据库中的数据进行查找,接下来我们将具体的介绍 Mybatis 的增删查改。
同时本篇文章还详细介绍了 $ 和 # 的区别(重点)。
1. 数据的查找 select
1.1 查询所有数据
通过 workbench,我们可以看到数据库中的现有数据:
接下来我们在 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="queryAll" resultType="com.example.demo.model.User">select * from userinfo</select>
</mapper>
接下来在 UserMapper 类中添加查找方法:
@Mapper
public interface UserMapper {List<User> queryAll();
}
在上述类的方法中右键,选择生成,选择测试,即可生成测试类。接下来,在 UserMapperTest 类中进行自测:
@Slf4j
@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid queryAll() {List<User> users = userMapper.queryAll();log.info(users.toString());}
}
运行结果如下图所示,可以看到查找到了数据库中现有的所有数据:
1.2 通过 id 进行查找
首先,我们在 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="queryAll" resultType="com.example.demo.model.User">select * from userinfo</select><select id="queryById" resultType="com.example.demo.model.User">select * from userinfo where id = ?</select>
</mapper>
接下来在 UserMapper 类中添加查找方法:
@Mapper
public interface UserMapper {// 查询所有数据List<User> queryAll();// 通过 id 查询数据User queryById(@Param("uid") Integer id);
}
在上述类的方法中右键,选择生成,选择测试,生成测试类:
可以看到生成的测试类中包含以下方法:
接下来,我们编写测试的代码:
@Slf4j
@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid queryAll() {List<User> users = userMapper.queryAll();log.info(users.toString());}@BeforeEachvoid setUp() {log.info("before...");}@AfterEachvoid tearDown() {log.info("after...");}@Testvoid queryById() {log.info("queryById...");User user = userMapper.queryById(1);log.info(user.toString());}
}
运行结果如下:
需要注意的是,当我们只有一个参数时,可以不写注解;但是写了注解时,SQL 语句需要和注解中使用的参数保持一致:
当只有一个参数时,不仅可以不写参数,参数的名称也可以不同,以下两条语句均可以成功执行:
User queryById(Integer id);
User queryById(Integer aaa);
2. 插入数据 insert
我们先来看一下数据库中都有哪些数据:
可以看到 id 是自增的,createtime 和 updatetime 都是当前时间,因此我们在插入数据时不用添加。
接下来我们在 UserMapper.xml 文件中添加以下内容:
<insert id="insert">insert into userinfo (username,password,photo)values(#{username},#{password},#{photo})
</insert>
在 UserMapper 类中添加插入方法:
@Mapper
public interface UserMapper {// 查询所有数据List<User> queryAll();// 通过 id 查询数据User queryById(@Param("uid") Integer id);// 插入数据Integer insert(User user);
}
在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:
@Slf4j
@SpringBootTest
class UserMapperTest {@Autowiredprivate UserMapper userMapper;@Testvoid queryAll() {List<User> users = userMapper.queryAll();log.info(users.toString());}@BeforeEachvoid setUp() {log.info("before...");}@AfterEachvoid tearDown() {log.info("after...");}@Testvoid queryById() {log.info("queryById...");User user = userMapper.queryById(1);log.info(user.toString());}@Testvoid insert() {User user = new User();user.setUsername("Danny");user.setPassword("123456");user.setPhoto("123");Integer result = userMapper.insert(user);log.info("insert result:"+ result);}
}
接下来,在 UserMapperTest 类中进行自测:
在 workbench 中可以看到数据插入成功:
还可以通过第二种方法插入数据:
Integer insert2(@Param(("userinfo")) User user);
<insert id="insert2">insert into userinfo (username,password,photo)values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})</insert>
添加测试方法:
@Testvoid insert2() {User user = new User();user.setUsername("Danny");user.setPassword("123456");user.setPhoto("123");Integer result = userMapper.insert2(user);log.info("insert result:"+ result);}
可以看到,同样能够运行成功:
需要注意的是,如果使用了 @Param 注解,就必须使用 @Param 注解的命名。
获取自增 id
在 UserMapper.xml 文件中添加以下内容:
<insert id="insert2" useGeneratedKeys="true" keyProperty="id">insert into userinfo (username,password,photo)values(#{userinfo.username},#{userinfo.password},#{userinfo.photo})</insert>
在 UserMapperTest 类中添加以下内容:
@Testvoid insert2() {User user = new User();user.setUsername("Danny");user.setPassword("123456");user.setPhoto("123");Integer result = userMapper.insert2(user);log.info("insert result:"+ result + ", 自增id:" + user.getId());}
此时可以看到自增 id 为:5.
3. 修改数据 update
在修改数据库的数据之前,我们先来看一下此时数据库中的数据:
接下来我们修改 id = 3 的这行数据:
在 UserMapper.xml 文件中添加以下内容:
<update id="update">update userinfo set username = #{username},password = #{password} where id = #{id}</update>
在 UserMapper 类中添加修改方法:
@Mapper
public interface UserMapper {// 查询所有数据List<User> queryAll();// 通过 id 查询数据User queryById(@Param("uid") Integer id);// 插入数据Integer insert(User user);Integer insert2(@Param(("userinfo")) User user);// 修改数据void update(User user);
}
在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:
@Testvoid update() {User user = new User();user.setId(3);user.setUsername("Jenny");user.setPassword("root123456");userMapper.update(user);}
接下来,在 UserMapperTest 类中进行自测:
运行成功后,可以看到数据库中 id =3 这一行的数据已经被修改了:
4. 删除数据 delete
我们先来看一下此时数据库中的数据:
接下来我们删除 id = 4 的这行数据:
在 UserMapper.xml 文件中添加以下内容:
<delete id="delete">delete from userinfo where id = #{id}</delete>
在 UserMapper 类中添加修改方法:
@Mapper
public interface UserMapper {// 查询所有数据List<User> queryAll();// 通过 id 查询数据User queryById(@Param("uid") Integer id);// 插入数据Integer insert(User user);Integer insert2(@Param(("userinfo")) User user);// 修改数据void update(User user);// 通过 id 删除数据void delete(Integer id);
}
在上述类的方法中右键,选择生成,选择测试,即可生成测试类,编写测试方法:
@Testvoid delete() {userMapper.delete(4);}
接下来,在 UserMapperTest 类中进行自测:
运行成功后,可以看到数据库中 id =4 这一行的数据已经被删除了:
5. $ 和 # 的区别
我们将通过 id 查询的 xml 文件中的 # 改成 $:
<select id="queryById" resultType="com.example.demo.model.User">select * from userinfo where id = ${uid}</select>
可以看到依然可以查询到数据:
接下来我们使用 # 通过名称来查询:
User queryByName(String name);
<select id="queryByName" resultType="com.example.demo.model.User">select * from userinfo where username = #{username}</select>
@Testvoid queryByName() {log.info("queryByName...");User user = userMapper.queryByName("Jenny");log.info(user.toString());}
可以看到成功查询到数据:
接下来我们使用 $ 通过名称来查询:
修改 xml 文件:
<select id="queryByName" resultType="com.example.demo.model.User">select * from userinfo where username = ${username}</select>
运行代码后出现以下报错:
是因为 $ 符号传递的参数是没有加引号,直接放在参数中的。
那么,如果我们自行添加引号呢?
<select id="queryByName" resultType="com.example.demo.model.User">select * from userinfo where username = '${username}'</select>
此时我们可以看到成功运行了:
综上,我们来看一下其中的本质原因:
- #{} :预编译处理(占位)
- ${}:字符直接替换(字符的内容直接当作 SQL 的一部分来运行,SQL 注入影响程序的安全)
当我们执行以下 SQL 语句时,会发现数据库中的所有数据都被查出来了:
SELECT * FROM userinfo where username = '' or 1 = '1'
5.1 SQL 注入 用户登录
我们先在数据库中保留以下信息:
我们先来测试 # 正确查询:
<select id="queryByNameAndPassword" resultType="com.example.demo.model.User">select * from userinfo where username = #{username} and password = #{password}</select>
User queryByNameAndPassword(@Param("username") String username,@Param("password") String password);
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "admin";User user = userMapper.queryByNameAndPassword(username,password);log.info(user.toString());}
可以看到查询成功:
再来测试 # 错误查询,直接修改测试方法:
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "admin123";User user = userMapper.queryByNameAndPassword(username,password);log.info(user==null?null:user.toString());}
可以看到查询为空:
接下来测试 SQL 注入的情况:
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "'or 1 ='1";User user = userMapper.queryByNameAndPassword(username,password);log.info(user==null?null:user.toString());}
可以看到使用错误的 password 显示为 null ,数据没有被查询出来:
接下来测试 $ 正确查询:
<select id="queryByNameAndPassword" resultType="com.example.demo.model.User">select * from userinfo where username = '${username}' and password = '${password}'</select>
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "admin";User user = userMapper.queryByNameAndPassword(username,password);log.info(user==null?null:user.toString());}
可以看到成功查询到:
接下来我们输入错误的 password :
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "admin123";User user = userMapper.queryByNameAndPassword(username,password);log.info(user==null?null:user.toString());}
可以看到没有查询到数据,显示为 null:
接下来我们来看 SQL 注入的情况:
@Testvoid queryByNameAndPassword() {String username = "admin";String password = "'or 1 ='1";User user = userMapper.queryByNameAndPassword(username,password);log.info(user==null?null:user.toString());}
可以看到数据仍然被查询出来了:
那么 SQL 注入简单来说就是将本来不该被查询出来的数据,查询出来了。
6. Spring Boot 打印 SQL 日志
添加配置文件:
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
可以看到直接将对应的 SQL 语句打印在了控制台:
以上就是 $ 打印的结果,是直接进行替换了。
接下来我们看一下 # 打印的结果:
所以,# 就是进行预编译,采用占位的方式。
7. order by 排序
我们先来看一下此时数据库中的数据:
接下来我们根据 id 进行降序排列:
List<User> queryByOrder(String order);
<select id="queryByOrder" resultType="com.example.demo.model.User">select * from userinfo order by id #{order}</select>
@Testvoid queryByOrder() {List<User> users = userMapper.queryByOrder("desc");log.info(users.toString());}
我们可以看到运行之后的结果并没有像我们想象之中的那样降序排列,而是直接报错:
那么上述这种情况下,就无法使用 # 必须使用 $ 符号才可以。
所以排序时只能使用 $。
修改 xml 文件:
<select id="queryByOrder" resultType="com.example.demo.model.User">select * from userinfo order by id ${order}</select>
此时可以看到成功进行了降序排列:
但是只要使用了 $ 就存在 SQL 注入的问题,该如何解决呢?
要从根源上解决以上问题就是不让用户输入 SQL 语句,用户只能点击,参数由后端进行拼接,后端在查询之前,对参数进行校验,只能传两个值:desc(降序) 和 asc(升序)。
8. like 查询
我们先来看一下 SQL 语句的 like 查询:
SELECT * FROM userinfo where username like "%m%";
接下来我们使用 # 来查询:
List<User> queryByLike(String name);
<select id="queryByLike" resultType="com.example.demo.model.User">SELECT * FROM userinfo where username like '%#{name}%';</select>
@Testvoid queryByLike() {List<User> users = userMapper.queryByLike("m");log.info(users.toString());}
可以看到无法正确查询:
接下来我们改成 $ 进行查询:
<select id="queryByLike" resultType="com.example.demo.model.User">SELECT * FROM userinfo where username like '%${name}%';</select>
此时,我们可以看到查询到了数据:
但是使用了 $ 就存在 SQL 注入的问题,因此我们需要使用 MySQL 的一个内置函数。
SELECT * FROM userinfo where username like concat('%','m','%');
可以看到使用以上 SQL 语句也可以进行查询:
因此,我们修改 xml 文件如下:
<select id="queryByLike" resultType="com.example.demo.model.User">SELECT * FROM userinfo where username like concat('%',#{name},'%');</select>
可以看到使用 concat 函数后可以查询成功:
9. 通过页面返回数据
新建 UserController 类:
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/selectAll")public List<User> selectAllUser(){return userService.selectAllUser();}
}
新建 UserService 类:
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public List<User> selectAllUser() {return userMapper.queryAll();}
}
接下来我们运行启动类,即可在页面中看到查找的数据库信息:
10. 总结
- $ 存在 SQL 注入问题,因为 # 是预编译而 $ 是字符替换;
- orde by 只能使用 $,通过后端控制参数的输入(只能为 desc 和 asc);
- # 直接用于 like 查询会报错,需要使用 MySQL 的内置函数 concat 进行字符连接。