文章目录
MyBatis
1. MyBatis是什么?
MyBatis是一款数据持久层框架,它支持自定义SQL、存储过程(很少使用)以及高级映射,MyBatis去除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
简单来说MyBatista是简单完成程序和数据交互的工具,就是是更简单的操作和读取数据库的工具。
2. 为什么要学习MyBatis
对于Java后端程序员来说,程序无非就是有两个部分构成的
- 程序
- 数据库
后端程序员无非就是进行数据库的CRUD。
而这两个重要的组成部分要进行通讯,就要依靠数据库连接工具,典型的就是JDBC,为什么有了JDBC还要去学习MyBatis这个框架呢?JDBC大概有这么几个流程:
- 去Maven仓库下载JDBC的jar包并导入
- 创建数据库,在代码里设置用户名和密码创建DataSource获取Connection连接
- 编写带占位符
?
的SQL语句 - 把占位符
?
替换成要操作的字段名并指定类型 - 通过connection获取Statement对象
- 通过Statement执行SQL获得ResultSet结果集或影响行数
- 处理结果集
- 释放资源
对于JDBC每操作一次数据库都要进行一次又一次的重复操作,建立连接、拼装SQL、执行SQL、处理结果集、最后还得释放资源。
为了解决这一系列的麻烦操作,就可以使用MyBatis来更简单的操作数据库。
3. 第一个MyBatis环境搭建
这是一个简单的框架交互流程图
- Mapper就是我们所说的数据持久层
- Inteface就是我们在类中写的接口,接口里的方法没有具体实现的,一个接口对应一张表。表里的方法对应一些操作
- 在xml中是接口方法的实现,SQL的编写,xml是MyBatis提供的固定写法
- 在程序运行的时候,MyBatis会将Inteface和xml合二为一,生成SQL调用JDBC
- 再把结果从Mapper返回给Service层
MyBatis也是一个ORM框架,ORM(Object Relational Mapping),即对象关系映射。在面向对象的编程语言中,奖关系数据库中的数据对象建立起映射关系,进而自动的完成数据与对象的互相转换:
- 将输入数据(即传入对象)+ SQL映射成原生SQL
- 将结果集映射返回对象,即为输出对象
ORM把数据库映射为对象:
- 数据库表(table)——>类(class)
- 记录(record,行数据)——> 对象(Object)
- 字段(field) ——> 对象的属性(attribute)
一般的ORM框架,会将数据库模型的每张表都映射为一个Java类。也就是说,使用MyBatis可以像操作对象一样来操作数据表,可以实现对象和数据库之间的转换。
1)添加MyBatis框架支持
老项目添加MyBatis
需要添加两个框架的引用
- MyBatis框架
- MySQL驱动
2)配置MyBatis相关配置文件
1.配置数据连接信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver #mysql 8.0之前的写法
#driver-class-name=com.mysql.cj.jdbc.Driver 这是8.0之后的写法
2.MyBatis的XML文件位置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/blog_system?characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver #mysql 8.0之前的写法
#driver-class-name=com.mysql.cj.jdbc.Driver 这是8.0之后的写法
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
配置文件中的xml文件位置要和创建的文件夹名对应
3)添加代码
UserInfo对象
注意:这里的属性名要和数据库中的字段名对应
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserInfo {
private int userId;
private String username;
private String password;
}
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.model.UserInfo">
select * from user
</select>
</mapper>
控制器代码
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
@RequestMapping("/getAll")
public List<UserInfo> getAll() {
return userService.getAll();
}
}
Service代码
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public List<UserInfo> getAll() {
return userMapper.getAll();
}
}
定义接口代码
@Mapper
public interface UserMapper {
// 方法定义
List<UserInfo> getAll();
}
通过浏览器访问
4. 解决类的属性名和数据表字段名不一致(resultMap)
实体类
@Getter
@Setter
public class UserInfo {
private int id;
private String name;
private String password;
}
数据表
+----------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+----------------+
| userId | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(64) | YES | UNI | NULL | |
| password | varchar(32) | YES | | NULL | |
+----------+-------------+------+-----+---------+----------------+
MyBatis配置文件
<?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">
<resultMap id="UserBean" type="com.example.demo.model.UserInfo">
<!-- 映射主键的(表中主键和程序实体类中的主键) -->
<id column="userId" property="id"></id>
<!-- 普通列的映射 -->
<result column="username" property="name"></result>
<result column="password" property="password"></result>
</resultMap>
<select id="getAll" resultMap="UserBean">
select * from user
</select>
</mapper>
通过resultMap
来解决类的属性名和数据表中字段名不一致的情况
id
标签表示主键result
标签表示普通字段
MyBatis查询返回类型的设置
- resultType(返回结果类型)
- resultMap(返回映射)
resultType
VSresultMap
- 共同点:它们的功能是一样的,都是用来进行指定结果类型
- 不同点:
resultType
用法简单,但是如果实体类中的属性名和表中的字段名不一致那么结果将查询不出来resultMap
用法相对麻烦(resultMap接口多个列),但它可以实现属性名和数据表字段名不对应的映射,也能查询出结果
5. 增加操作
1)返回受影响的行数
Controller(控制器)代码:
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
public Integer addUser(UserInfo userInfo) {
// 校验参数
if (userInfo == null || userInfo.getName() == null || "".equals(userInfo.getName())
|| userInfo.getPassword() == null || "".equals(userInfo.getPassword())) {
return 0;
}
return userService.addUser(userInfo);
}
}
Service(服务层代码):
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int addUser(UserInfo userInfo) {
// 服务(方法编排)
return userMapper.addUser(userInfo);
}
}
Mapper 接口代码:
@Mapper
public interface UserMapper {
int addUser(UserInfo userInfo);
}
编写MyBatis的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">
<!-- 添加方法 -->
<insert id="addUser">
insert into user(username,password) values(#{name},#{password})
</insert>
</mapper>
通过postman测试
2)返回自增的id
Controller(控制器)代码:
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
@RequestMapping("/add2")
public Integer addUser2(UserInfo userInfo) {
// 校验参数
if (userInfo == null || userInfo.getName() == null || "".equals(userInfo.getName())
|| userInfo.getPassword() == null || "".equals(userInfo.getPassword())) {
return 0;
}
// 调用数据库执行添加操作,执行完添加之后会将自增id设置到userinfo的id属性
userService.addUser2(userInfo);
return userInfo.getId();
}
}
Service(服务层代码):
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int addUser2(UserInfo userInfo) {
// 服务(方法编排)
return userMapper.addUser2(userInfo);
}
}
Mapper接口:
@Mapper
public interface UserMapper {
int addUser2(UserInfo userInfo);
}
编写MyBatis的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">
<!-- 添加方法(返回自增id) -->
<insert id="addUser2" useGeneratedKeys="true" keyProperty="id" keyColumn="userId">
insert into user(username, password) values(#{name},#{password})
</insert>
</mapper>
- useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据
库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递
增字段),默认值:false - keyColumn:设置⽣成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列
不是表中的第⼀列的时候,是必须设置的。如果⽣成列不⽌⼀个,可以⽤逗号分隔多个属性
名称 - keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值
或 insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)。如果⽣成列不⽌
⼀个,可以⽤逗号分隔多个属性名称
Postman测试结果
6. 修改操作
Controller(控制器代码)
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
/**
* 修改密码操作
* @param id
* @param password
* @return
*/
@RequestMapping(value = "/update")
public Integer updateUserInfo(Integer id, String password) {
if(id == null || id < 0 || password == null || "".equals(password)) {
return 0;
}
return userService.updateUserInfo(id,password);
}
}
Service服务层代码:
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int updateUserInfo(Integer id, String password) {
return userMapper.updateUserInfo(id,password);
}
}
Mapper接口方法定义
@Mapper
public interface UserMapper {
int updateUserInfo(Integer id, String password);
}
MyBatis配置文件实现接口
<?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">
<!-- 更新操作 -->
<update id="updateUserInfo">
update user set password=#{password} where userId=#{id}
</update>
</mapper>
7. 删除操作
Controller控制器代码:
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
@RequestMapping("/delete")
public Integer deleteUser(Integer id) {
if (id == null || id < 0) {
return 0;
}
return userService.deleteUser(id);
}
}
Service服务层代码
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public int deleteUser(int id) {
return userMapper.deleteUser(id);
}
}
Mapper接口方法定义
@Mapper
public interface UserMapper {
int deleteUser(int id);
}
MyBatisj配置文件接口实现
<?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">
<!-- 删除操作 -->
<delete id="deleteUser">
delete from user where userId=#{id}
</delete>
</mapper>
8. 参数赋值的两种方式
#{}
:预编译处理${}
:字符直接替换
预编译处理是指:MyBatis在处理#{}
时,会将SQL中的#{}
替换为?
号,使用PreparedStatement的set方法来赋值。直接替换:是MyBatis在处理${}
时,就是直接把${}
替换成变量的值了。
${}
使用场景:
假设我们要对用户以id进行降序排序,变量order传递过来的是asc或者desc
@Mapper
public interface UserMapper {
// 方法定义
List<UserInfo> sortAllUser(String order);
}
<!-- ${}使用场景 -->
<select id="sortAllUser" resultMap="UserBean">
select * from user order by userId ${order}
</select>
假设传递过来的是desc,如果使用${}
的话,${order}
就会被直接替换成 desc
,正常排序返回结果。而如果使用#{}
的话就会被替换成‘desc’
,此时'desc'
就是一个字符串了,那么执行SQL就会发生报错。
-- 使用${}
select * from user order by userId desc;
-- 使用#{}
select * from user order by userId 'desc';
而${}
是存存在SQL注入的问题的
SQL注入问题
假设要做一个简单的登录
Controller控制器代码
@Controller
@RequestMapping("/mybatis")
@ResponseBody
public class UserController2 {
@Autowired
private UserService userService;
/**
* 登录
* @param username
* @param password
* @param request
* @return
*/
@RequestMapping("/login")
public Object logIn(String username, String password, HttpServletRequest request) {
Map<String,Object> result = new HashMap<>();
result.put("status",200);// 响应状态码
String message = null;
int state = -1; // 登录状态码
if (username != null && !"".equals(username) && password != null && !"".equals(password)) {
UserInfo userInfo = userService.logIn(username,password);
if (userInfo != null) {
message = "登录成功";
state = 1;
// 把用户信息存储到session
HttpSession session = request.getSession(true);
session.setAttribute("userInfo",userInfo);
} else {
message = "用户名或密码错误";
}
} else {
message = "用户名或密码错误";
}
result.put("message",message);
result.put("state",state);
return result;
}
}
UserService服务层代码
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public UserInfo logIn(String username, String password) {
return userMapper.logIn(username,password);
}
}
Mapper接口映射
@Mapper
public interface UserMapper {
// 方法定义
List<UserInfo> getAll();
UserInfo logIn(String username, String password);
}
实现接口的xml配置文件
<!-- 登录用户查询 -->
<select id="logIn" resultMap="UserBean">
select * from user where username='${username}' and password='${password}'
</select>
如果使用${}
方式直接替换参数,就会有SQL注入的风险
Postman模拟请求
http://127.0.0.1:8080/mybatis/login?username=admin&password=' or userId='1''
无论输入任何密码都能登录成功,最后执行的SQL如下。也就是说通过SQL注入不需要知道密码就能登录,甚至执行删表操作。
mysql> select * from user where username='admin' and password='' or userId='1';
如果把${}
替换成#{}
就不会有SQL注入的问题
<select id="logIn" resultMap="UserBean">
select * from user where username=#{username} and password=#{password}
</select>
如果用#{}
是 会将SQL中的#{}
替换为?
号,使用PreparedStatement的set方法来赋值。把整体看做是一个字符串。
使用刚才的SQL注入执行的SQL将会是如下
mysql> select * from user where username="admin" and password="' or userId='1";
所以,${}
一定要慎用,如果非要使用就要将前端的参数进行安全过滤。
安全的模糊查询(like)
ike 使⽤ #{} 报错
这是一个错误示例
<!-- 过滤查询 -->
<select id="getLike" resultMap="UserBean">
select username from user where username like '#{keyName}%';
</select>
相当于执行的SQL是
select username from user where username like '"张"%';
这个是不能直接使⽤ ${},可以考虑使⽤ mysql 的内置函数 concat() 来处理,实现代码如下 :
<!-- 过滤查询 -->
<select id="getLike" resultMap="UserBean">
select username from user where username like concat(#{keyName},'%')
</select>