目录
一、MyBatis框架简介
二、创建Mybatis项目
1、在Mysql中创建要进行操作的数据库和数据表。
2、创建项目并添加MyBatis依赖。
3、配置MyBatis连接字符串以及保存的xml目录。
4、添加业务代码
三、利用MyBatis操作数据库
增加数据
删除数据
修改数据
查询数据
like 模糊查询
多表查询
四、动态SQL
if标签
trim标签
where标签
set标签
foreach标签
MyBatis是一个持久化框架,支持自定义SQL、存储过程以及高级映射,是一个优秀的ORM(对象关系映射)的框架。
MyBatis框架的特点就是比较灵活。
#配置数据库的连接信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
配置数据库连接信息:
#配置数据库的连接信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mycnblog?characterEncoding=utf8
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
xml文件保存路径:
#设置MyBatis的xml保存路径
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
a、创建model包,添加UserInfo实体类:
@Data
public class UserInfo {
private Integer id;
private String username;
private String password;
private String photo;
private String createTime;
private String updateTime;
private int state;
}
b、创建mapper包,定义UserMapper接口:
@Mapper
public interface UserMapper {
public UserInfo getUserInfoById(@Param("id") Integer id);
}
在resources目录下创建mybatis文件夹,创建UserMapper.xml文件,文件内容如下,其中select标签的id表示要执行的方法名,resultType表示返回的类的路径:
创建service包,创建UserService类,获取到UserMapper并实现getUserInfoById方法:
@Service
public class UserService {
@Resource
public UserMapper userMapper;
public UserInfo getUserInfoById(Integer id){
return userMapper.getUserInfoById(id);
}
}
创建controller包,创建UserController类,获取到UserService类并实现getUserInfoById方法:
@Controller
public class UserController {
@Autowired
private UserService userService;
@ResponseBody
@RequestMapping("/getuserinbyid")
public UserInfo getUserInfoById(Integer id){
if(id == null){
return null;
}
return userService.getUserInfoById(id);
}
}
访问验证:
在操作数据库之前,首先了解一下单元测试,单元测试可以针对某一方法进行测试,并且不需要再启动SpringBoot项目,使用单元测试有如下好处:
- 使用单元测试可以非常直观、简单地测试某一功能的正确性;
- 使用单元测试可以帮助在打包的时候发现一些错误,因为在打包之前要求所有的单元测试都必须通过。
- 使用单元测试不会对原有的数据库造成污染。
如果不想对数据库造成污染,就需要添加@Transactional注解,其原理是先执行sql语句,在进行回滚操作。
使用单元测试的一般步骤:
先生成单元测试的类:
在接口中右击鼠标,单击generate:
选择test,出现如下界面:
点击ok之后就会自动生成测试类。
增加一条用户信息,并返回受影响的行数。
首先需要在UserMapper接口中声明方法。
//添加用户信息,返回受影响的行数
public int addUserInfo(UserInfo userInfo);
然后需要在resources文件夹下的MyBatis文件下的UserMapper.xml文件中新增如下的insert标签:
insert into userinfo(id,username,password,photo) values(#{id},#{username},#{password},#{photo})
在生成的测试类中编写代码:
@Test
void addUserInfo() {
UserInfo userInfo = new UserInfo();
userInfo.setId(2);
userInfo.setUsername("lucy");
userInfo.setPassword("1234");
userInfo.setPhoto("test.jpg");
int i =userMapper.addUserInfo(userInfo);
Assertions.assertEquals(1,i);//使用断言测试返回受影响的行数是否为1
}
启动单元测试的结果:
由于当时在新建数据表时id是默认自增的,就需要在xml文件中的 insert添加如下属性:
insert into userinfo(username,password,photo) values(#{username},#{password},#{photo})
删除指定id的用户信息,并返回受影响的行数。
在UserMapper接口中定义删除数据的方法:
@Transactional //删除用户信息不污染数据库
//根据ID查询User信息
public int deleteUserInfo(@Param("id") Integer id);
在xml文件下添加delete标签:
delete from userinfo where id=#{id}
在测试类中编写代码:
//根据ID删除UserInfo信息
@Test
void deleteUserInfo() {
int i =userMapper.deleteUserInfo(3);
Assertions.assertEquals(1,i);//使用断言测试返回受影响的行数是否为1
}
启动单元测试运行结果:
修改指定id的用户名。
在UserMapper接口中定义修改数据方法:
//根据id修改用户名
public int updateUserInfo(@Param("id") Integer id,@Param("username") String username);
在xml文件中添加update标签:
update userinfo set username=#{username} where id=${id}
在生成的测试类中编写测试代码:
//根据id修改用户名
@Test
void updateUserInfo() {
int i = userMapper.updateUserInfo(2,"mary");
Assertions.assertEquals(1,i);//使用断言测试返回受影响的行数是否为1
}
测试运行结果:
根据id查询用户信息。
在UserMapper中定义查询方法:
@Transactional
//根据id查询UserInfo信息
public UserInfo getUserInfoById(@Param("id") Integer id);
在xml文件中添加select标签,其中resultType标签表示返回的对象所属类的路径
在测试类中编写测试代码:
//通过id查询用户信息
@Test
void getUserInfoById() {
UserInfo userInfo = userMapper.getUserInfoById(1);
Assertions.assertNotNull(userInfo);
}
测试运行结果:
在之前的xml文件中使用${}和#{} 这两种参数占位符,那么这两种占位符有如下区别:
定义不同:${}直接替换,#{}会进行预处理;
用法不同:${}只适用于整数替换,#{}适合所有的数据类型;
安全性不同:${}会导致sql注入安全问题,#{}的性能高,并且不存在安全问题。
${}如何会引起sql注入呢?
在userinfo信息表中有username和password两个属性,那么在查询的时候给password属性传入‘or 1= ‘1’’就会导致查出所有的用户信息包含用户的密码:
xml文件中新增如下的select标签:
使用如下测试代码:
//根据用户名和密码查询用户信息
@Test
void getUserInfoByNameAndPwd() {
String username = "admin";
String password = "''or 1 ='1'";
userMapper.getUserInfoByNameAndPwd(username,password);
}
查询到了所有的userinfo导致sql注入问题:
但是将参数占位符换为#{},再进行测试:
#{}占位符会进行预处理,不会直接进行替换,就不会出现sql注入的问题。
那么?{}就没有应用场景了吗?答案当然是否定的,任何东西存在即合理,当传递的参数是sql关键字或sql命令时就需要使用?{}占位符,因为需要直接替换,不需要#{}来进行预处理为字符串,否则就会报错。
在sql语句进行查询时也会经常用到like模糊查询,%表示0或多个字符,_表示一个字符,那么先使用?{}占位符来查询username中含有‘a’字符的用户信息。
xml的select标签的信息如下:
测试结果:
并不能完成直接替换,那么使用#{}参数占位符来进行测试,测试结果如下:
这两种参数占位符都不能直接实现模糊查询,就需要使用mysql中的内置函数concat来进行处理,将xml文件中的select标签的内容修改如下:
测试结果如下:
上述的操作都是在单表的基础上进行,但是查询还存在多表查询,常见的表关系有一对一、一对多和多对多,那么利用MyBatis就可以实现一对一和一对多的多表查询。
一对一的多表查询
在mycnblog数据库中有userinfo和articleinfo表,两个表的字段设置如下所示:
其中articleinfo表的uid对应userinfo表的的id。
查询文章对应的用户信息。
首先在model包下创建ArticleInfo类,将articleinfo表的字段作为ArticleInfo类的属性,并添加UserInfo属性。
@Data
public class ArticleInfo {
private int id;
private String title;
private String content;
private String createtime;
private String updatetime;
private int uid;
private int rcount;
private int state;
private UserInfo userInfo;
}
在Mapper包下创建ArticleMapper接口,定义id获取文章信息的方法:
//通过文章id来获取文章信息
public ArticleInfo getUserInfoByArticleInfoId(@Param("id") Integer id);
在mybatis包下创建ArticleMapper.xml文件,添加select标签,由于要实现多表查询,select标签的ResultType标签就无法满足需求了,就需要添加一个resultMap标签, 其id为标签名,type为要映射的实体类的路径,在该标签中设置主键(id)和其他普通的字段(result),其中column为数据库字段名,property为程序中的属性名,并且在UserMapper.xml文件中也添加resultMap标签,然后在ArticleMapper.xml的resultMap标签中继续添加association标签,property代表要连接的表在程序中的属性名,resultMap为UserMapper.xml文件中的resultMap标签名称,由于userinfo表和articleinfo表中有同名的字段名,就使用columnPrefix属性,表示在原有字段名前添加设置的columnPrefix属性值,UserMapper.xml文件的resultMap标签如下:
ArticleMapper.xml文件的内容如下:
最后在生成的test类中编写测试方法:
//通过id获取文章信息,实现一对一的多表查询
@Test
void getUserInfoByArticleInfoId() {
ArticleInfo articleInfo = articleMapper.getUserInfoByArticleInfoId(1);
log.info(String.valueOf(articleInfo));
}
测试结果:
createtime等字段为null因为在查询的时候就没有select这些字段。
一对多的多表查询
一个用户对应多篇文章,就需要使用一对多的多表查询。
先在UserInfo类中增加一个存放文章信息的集合:
private List atrlist;
然后再UserMapper接口中定义查询方法:
//根据id查询用户信息,并实现一对多的多表查询
public UserInfo getUserInfoAndArtInfoById(@Param("id") Integer id);
在UserMapper.xml文件的resultMap标签中添加collection标签,其属性与association标签的属性值类似:
再添加select标签:
在生成的测试类中编写如下的代码:
@Test
void getUserInfoAndArtInfoById() {
UserInfo userInfo = userMapper.getUserInfoAndArtInfoById(1);
log.info(String.valueOf(userInfo));
}
测试结果:
在上述的操作中都使用的是固定的参数,并且有时会多写,等都会出现报错,但动态SQL就能很好的解决上述问题。
if标签的test属性可以判断参数是否有值,如果没有值,就会隐藏if标签中的参数。
非必传的参数就可以使用if标签。
例如新增一个Userinfo信息,username和id为必传信息,但是photo信息为非必传。
那么UserMapper的insert标签如下:
insert into userinfo(username,password
,photo
)
values(#{username},#{password}
,#{photo}
)
在传入参数的时候未传入photo参数:
@Test
void addUserInfoByIf() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("老6");
userInfo.setPassword("1234");
int i = userMapper.addUserInfoByIf(userInfo);
Assertions.assertEquals(1,i);
}
测试结果:
trim标签有如下的属性:
- prefix:整个语句块,以prefix的值作为前缀;
- suffix:整个语句块,以suffix的值作为后缀;
- prefixOverrides:表示整个语句块要去除掉的前缀;
- suffixOverrides:表示整个语句块要去除掉的后缀。
那么, 上面新增一个Userinfo信息就可以使用trim标签,修改xml标签的内容如下:
insert into userinfo
username,password
,photo
values
#{username},#{password}
,#{photo}
测试结果:
where标签可以实现查询中的where语句替换,如果where中没有任何查询条件,那么就会删除where语句,如果有查询条件,就会自动生成相应的sql语句,并可以自动去除最前面的and标签,where标签通常搭配if标签来进行使用。
例如利用名字和密码查询用户信息:
UserMapper.xml的查询标签如下所示:
当传入用户名和密码之后测试结果如下:
where标签也可以使用
标签进行替换:
set标签是针对update更新语句的,可以代替update标签中的set关键字来完成。
set标签通常需要搭配if标签来进行使用。
例如利用id来修改用户信息。
在xml文件中的update标签如下:
update userinfo
username=#{username},
password=#{password}
where id=#{id}
在测试类中的测试代码如下:
@Test
void updateUserInfoById() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("王五");
userInfo.setPassword("9087");
userInfo.setId(3);
int i = userMapper.updateUserInfoById(userInfo);
Assertions.assertNotNull(i);
}
测试结果:
set标签也可以用trim标签进行替代
对集合遍历时通常会使用该标签,在in关键字之后通常使用,该标签有如下的属性:
collection:绑定方法参数的集合,如List、Set、Map以及数组等;
item:遍历时的每个对象;
open:语句块开头的字符串;
close:语句块结束的字符串;
separator:每次遍历之间的字符串。
例如删除id在指定集合的用户信息:
xml文件中的delete标签如下:
delete
from userinfo
where id in
#{uid}
在生成的test类编写的测试代码如下:
@Test
void deleteUserInfoByList() {
List list = new LinkedList<>();
list.add(4);
list.add(3);
list.add(5);
int i = userMapper.deleteUserInfoByList(list);
log.info(String.valueOf(i));
}
测试结果: