MyBatis是一款数据持久层框架,它支持自定义SQL、存储过程(很少使用)以及高级映射,MyBatis去除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。
简单来说MyBatista是简单完成程序和数据交互的工具,就是是更简单的操作和读取数据库的工具。
对于Java后端程序员来说,程序无非就是有两个部分构成的
后端程序员无非就是进行数据库的CRUD。
而这两个重要的组成部分要进行通讯,就要依靠数据库连接工具,典型的就是JDBC,为什么有了JDBC还要去学习MyBatis这个框架呢?JDBC大概有这么几个流程:
?
的SQL语句?
替换成要操作的字段名并指定类型对于JDBC每操作一次数据库都要进行一次又一次的重复操作,建立连接、拼装SQL、执行SQL、处理结果集、最后还得释放资源。
为了解决这一系列的麻烦操作,就可以使用MyBatis来更简单的操作数据库。
这是一个简单的框架交互流程图
MyBatis也是一个ORM框架,ORM(Object Relational Mapping),即对象关系映射。在面向对象的编程语言中,奖关系数据库中的数据对象建立起映射关系,进而自动的完成数据与对象的互相转换:
ORM把数据库映射为对象:
一般的ORM框架,会将数据库模型的每张表都映射为一个Java类。也就是说,使用MyBatis可以像操作对象一样来操作数据表,可以实现对象和数据库之间的转换。
老项目添加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文件位置要和创建的文件夹名对应
UserInfo对象
注意:这里的属性名要和数据库中的字段名对应
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class UserInfo {
private int userId;
private String username;
private String password;
}
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.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();
}
通过浏览器访问
实体类
@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配置文件
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
VSresultMap
resultType
用法简单,但是如果实体类中的属性名和表中的字段名不一致那么结果将查询不出来resultMap
用法相对麻烦(resultMap接口多个列),但它可以实现属性名和数据表字段名不对应的映射,也能查询出结果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配置文件
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测试
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配置文件
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="addUser2" useGeneratedKeys="true" keyProperty="id" keyColumn="userId">
insert into user(username, password) values(#{name},#{password})
insert>
mapper>
Postman测试结果
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配置文件实现接口
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>
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配置文件接口实现
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>
#{}
:预编译处理${}
:字符直接替换预编译处理是指: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注入的问题的
假设要做一个简单的登录
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";
所以,${}
一定要慎用,如果非要使用就要将前端的参数进行安全过滤。
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>