在前面我们操作数据库的时候都是使用JDBC,我们发现它的操作太繁琐,为了解决这个问题,就提出了MyBatis。MyBatis就是更加简单的完成程序和数据库交互的工具,MyBatis是一个ORM框架(Object Relational Mapping),也就是对象映射。
在该框架中:
数据库表(table)被映射为类(class);
记录(record,行记录)被映射会为对象(object);
字段(field)被映射为属性(attribute);
操作MyBatis主要分为两大步骤:配置MyBatis开发环境、使用MyBatis模式和语法操作数据库
MyBatis的使用
0.前置准备工作:初始化数据库(非必须步骤);
1.添加MyBatis框架的支持
a)老项目升级添加MyBatis
b)创建新项目的时候直接添加MyBatis
2.配置MyBatis相关的配置文件
2.1配置数据库连接信息;
-连接数据库服务器地址
-数据库用户名
-数据库密码
-数据库的驱动(数据库的类型)
上面的driver-class-name,如果你的mysql版本是在8.0之前的话就写成com.mysql.jdbc.Driver
如果你的masql版本是在8.0之后的话就写成com.mysql.cj.jdbc.Driver
2.2配置MyBatis中的XML路径
#设置mybatis的xml文件配置
mybatis.mapper-locations=classpath:mapper/**Mapper.xml
3.添加业务代码
业务代码之间的执行逻辑:
使用MyBatis来查询数据库
现在的业务逻辑用户想要拿到数据库中的数据,现在通过一个getAll()方法来进行业务之间的逻辑调用,各层级之间调用关系就如上面的关系所示,下面展示具体的层级里面每个类的具体代码实现:
因为controller要调用服务层,所以要将服务层里面的UserService类注入进来,controller层里面的UserController类中的业务代码:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getall")
public List<Userinfo> getAll(){
return userService.getAll();
}
}
因为service层要调用数据库持久层mapper,所以要将持久层里面的UserMapper接口注入进来,service层里面的UserService类中的业务代码:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public List<Userinfo> getAll() {
return userMapper.getAll();
}
}
因为持久层mapper它里面的方法声明和方法的实现是分开的,方法声明直接写在UserMapper类里面,而方法实现就要写在xml文件中:
model层实体类Userinfo里面的业务代码:
@Data
public class Userinfo {
private int id;
private String username;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
然后运行程序:
可以看到返回的是json格式的数据
在MyBatis Mapper.xml查询返回类型中,对于<select>查询标签来说至少需要两个属性:
- id属性:用于标识实现接口中的那个方法
- 结果映射属性:结果映射属性有两种实现标签——<resultMap>和<resultType>
1.resultType(返回结果类型)
2.resultMap(返回字典映射)
UserMapper中的方法实现不只有一种,当我们实体类中的字段名称个数据库中的字段名称不一样的时候,为了避免查询出错,就可以使用第二种方法:
实体类里面的userinfo:
@Data
public class Userinfo {
private int id;
private String name;
private String password;
private String photo;
private LocalDateTime createtime;
private LocalDateTime updatetime;
private int state;
}
UserMapper里面方法的具体实现:
<?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.myblog.mapper.UserMapper">
<!--方法一:使用resultType-->
<!-- <select id="getAll" resultType="com.example.myblog.model.Userinfo">-->
<!-- select * from userinfo-->
<!-- </select>-->
<!--方法二:使用resultMap-->
<resultMap id="BaseMap" type="com.example.myblog.model.Userinfo">
<!--映射主键的(表中主键和程序实体类中的主键)-->
<id column="id" property="id"></id>
<!--普通列的映射-->
<result column="username" property="name"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="getAll" resultMap="BaseMap">
select * from userinfo
</select>
</mapper>
查询结果:
resultType和resultMap的区别:
共同点:他们的功能是一样的,都是用来进行指定结果类型;
不同点:resultType用法简单,但是如果实体类中的属性名和表中的字段名不一致那么将查询不出结果。resultMap用法相对麻烦,但是可以实现属性和字段不一致的映射,让查询结果能够正常。而且一对一和一对多关系可以使用resultMap映射并查询数据。
使用MyBatis来添加数据到数据库
在使用MyBatis来添加数据到数据库中,分为两种情况:
1.返回受影响的行数;
2.返回自增的ID;
返回受影响的行数
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/*
* 添加用户(返回受影响的行数)
* */
@RequestMapping("/add")
public int add(Userinfo userinfo){
//控制层只负责参数效验,效验完以后在调用业务逻辑层
if(userinfo==null||userinfo.getName()==null
||userinfo.getPassword()==null
||userinfo.getName().equals("")
||userinfo.getPassword().equals("")
){//非法参数
return 0;
}
return userService.add(userinfo);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int add(Userinfo userinfo) {
//服务(方法编排)
return userMapper.add(userinfo);
}
}
@Mapper
public interface UserMapper {
int add(Userinfo userinfo);
}
<?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.myblog.mapper.UserMapper">
<insert id="add">
insert into userinfo(username,password)
value(#{name},#{password})
</insert>
</mapper>
结果:
返回自增ID
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/*
* 添加用户方法2:返回一个用户的自增id
* */
@RequestMapping("/add2")
public int add2(Userinfo userinfo){
//控制层只负责参数效验,效验完以后在调用业务逻辑层
if(userinfo==null||userinfo.getName()==null
||userinfo.getPassword()==null
||userinfo.getName().equals("")
||userinfo.getPassword().equals("")
){//非法参数
return 0;
}
//调用数据库执行添加操作,执行完添加之后将自增id设置到userinfo的id属性
userService.add2(userinfo);
return userinfo.getId();
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int add2(Userinfo userinfo) {
//服务(方法编排)
return userMapper.add2(userinfo);
}
}
@Mapper
public interface UserMapper {
int add2(Userinfo userinfo);
}
<?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.myblog.mapper.UserMapper">
<!--添加方法2:返回自增id-->
<insert id="add2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into userinfo(username,password)
value (#{name},#{password})
</insert>
</mapper>
里面的useGeneratedKeys表示是否开启自增主键,true表示开启,false表示关闭;keyProperty表示指定唯一能够识别的属性,keyColumn表示将自增的id插入到当前项目对象类中的指定的主键属性上。
结果:
@Test
void add2() {
Userinfo userinfo = new Userinfo();
userinfo.setName("王五");
userinfo.setPassword("4321");
//调用需要进行单元测试的方法
int id = userController.add2(userinfo);
System.out.println("自增ID:"+id);
}
使用MyBatis来修改数据到数据库
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/*
* 修改密码
* */
@RequestMapping("/update")
public int updatePassword(int id,String password){
//参数效验
if(id<=0||password==null||password.equals("")){//非法参数
return 0;
}
return userService.updatePassword(id,password);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int updatePassword(int id, String password) {
return userMapper.updatePassword(id, password);
}
}
@Mapper
public interface UserMapper {
int updatePassword(int id, String password);
}
<?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.myblog.mapper.UserMapper">
<!--修改方法-->
<update id="updatePassword">
update userinfo set password=#{password} where id=#{id}
</update>
</mapper>
@SpringBootTest
class UserControllerTest {
@Autowired
private UserController userController;
@Test
void updatePassword() {
int result = userController.updatePassword(3,"lisi");
Assertions.assertEquals(result,1);
}
}
使用MyBatis删除数据库中的数据
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/*
* 删除数据
* */
@RequestMapping("/del")
public int del(Integer id){
if(id==null||id<=0){
return 0;
}
return userService.del(id);
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public int del(int id) {
return userMapper.del(id);
}
}
@Mapper
public interface UserMapper {
int del(int 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.myblog.mapper.UserMapper">
<!--删除方法-->
<delete id="del">
delete from userinfo where id=#{id}
</delete>
</mapper>
@SpringBootTest
class UserControllerTest {
@Autowired
private UserController userController;
@Test
void del() {
int result = userController.del(6);
Assertions.assertEquals(result,1);
}
}
MyBatis中参数赋值的两种方式:
#{}:相当于JDBC里面替换暂占位符的操作方式;
例如:查找数据库中姓名为李四的用户信息
select * from userinfo where username='李四';
${}:相当于直接替换
例如:查找数据库中姓名为李四的用户信息
select * from userinfo where username=李四;
两种方法的区别:
#{}:预编译处理,可以防止SQL注入的问题;
${}:相当于直接替换(主要使用场景——用于SQL关键字的替换),例如:select * from userinfo order by id ${order},但是该方法不能预防SQL注入(存在安全问题)
SQL注入问题
在上面讨论了${}参数赋值的方法,这个方法有一个特点就是不能防止SQL注入的问题,下面来看一下什么是SQL注入:
为了好展示SQL注入的问题问题,现在就将数据库中的数据改为只剩一条:
然后现在验证一个登录操作:
@Controller
@ResponseBody
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/login")
public Object login(String username,String password){
HashMap<String,Object> result = new HashMap<>();
result.put("succ",200);//后端响应状态码
String msg = "";
int state = -1;//登录状态码
if(username!=null&&password!=null&&username.equals("")
&&password.equals("")){
//验证用户名和密码是否正确
Userinfo userinfo = userService.login(username,password);
if(userinfo!=null&&userinfo.getId()>0){
//查询到了内容(用户名和密码正确)
state = 1;
System.out.println("登陆成功:"+userinfo);
}else{
msg = "用户名或密码输入错误,请先检查!";
}
}else{
msg = "用户名或密码输入错误,请先检查!";
}
result.put("state",state);
result.put("msg",msg);
return result;
}
}
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public Userinfo login(String username, String password) {
return userMapper.login(username,password);
}
}
@Mapper
public interface UserMapper {
Userinfo login(String username, String password);
}
<?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.myblog.mapper.UserMapper">
<resultMap id="BaseMap" type="com.example.myblog.model.Userinfo">
<!--映射主键的(表中主键和程序实体类中的主键)-->
<id column="id" property="id"></id>
<!--普通列的映射-->
<result column="username" property="name"></result>
<result column="password" property="password"></result>
<result column="photo" property="photo"></result>
<result column="createtime" property="createtime"></result>
<result column="updatetime" property="updatetime"></result>
<result column="state" property="state"></result>
</resultMap>
<select id="login" resultMap="BaseMap">
select * from userinfo where username='${username}' and password='${password}'
</select>
</mapper>
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void login() {
Userinfo userinfo = userMapper.login("李四", "' or 1='1");
System.out.println(userinfo);
}
}
分析产生以上一个结果的原因:
首先因为${}它是直接替换,在执行Mapper.xml文件时,它就将"李四","' or 1 = '1"直接替换到SQL语句中:select * from userinfo where username='李四' and password= '' or 1 = '1';这条SQL语句的前半句select * from userinfo where username='李四' and password= '';它是一个完整的逻辑正确的SQL,后半句也是逻辑正确的SQL,这样一组合,因为后半句一定是正确的,所以他就能将相应的SQL信息查询出来。
SQL注入作为影响系统安全的一种常见方式不仅可以获取到数据库中的数据,还可以将数据库中的数据给删除掉。
所以在用于查询字段的时候,尽量使用#{}预查询的方式。
like查询
在我们使用like来进行模糊查询的时候,如果写成这样select * from userinfo where username like '%#{username}%';查询出来会报错,它相当于select * from userinfo where username like '%'username'%'; ,但是如果使用${}的话虽然可以查询出来,但是这样就会面临SQL注入的问题。
为了解决这一问题,就需要使用到mysql的内置函数concat()来处理:select * from userinfo where username like concat('%',#{username},'%');