目录
2.2.2 配置MyBatis的XML保存路径(MyBatis运行时使用)
一、MyBatis定义
MyBatis是一款优秀的持久层框架。它⽀持⾃定义 SQL、存储过程以及⾼级映射。MyBatis 去除了⼏乎所有的 JDBC 代码以及设置参数和获取结果集的⼯作。MyBatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通⽼式 Java 对象)为数据库中的记录。
之前学习的JDBC,整个操作⾮常的繁琐,不仅要拼接每⼀个参数,⽽且还要按照模板代码的⽅式,⼀步步的操作数据库,并且在每次操作完,还要⼿动关闭连接等, ⽽所有的这些操作步骤都需要在每个⽅法中重复书写。
MyBatis 是一种更简单完成程序和数据库交互的⼯具,学习MyBatis可以帮助我们更简单的操作和读取数据库⼯具。
二、Mybatis开发环境的配置
2.1 引入MyBatis框架到项目中
2.2.1 新项目引入MyBatis框架
MyBatis在进行映射时,数据模型有多个,在该项目中,使用的是MySQL数据库,所以需要将MySQL的依赖也导入进来。
新建的MyBatis项目,如果没有配置数据库的连接信息,项目是启动不起来的,即需要指明连接的是哪个数据库。 如何配置将会在下文中介绍。
2.2.2 老项目引入MyBatis框架
①安装EditStartes插件
②添加依赖
MyBatis在映射数据模型时,可能有多个。我们使用的是MySQL,因此要指定映射的数据模型是MySQL。
这步千万不能省略哦!!!
2.2 配置MySQL的服务器地址、用户名和密码
2.2.1 配置数据库连接信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/messagewall?characterEncoding=utf8&useSSL=false
username: root
password: 1111
driver-class-name: com.mysql.jdbc.Driver
driver-class-name:是指明数据库的类型
如果是MySQL8之前,使用com.mysql.jdbc.Driver
如果是MySQL8之后,使用com.mysql.cj.jdbc.Driver
设置好后,如果程序能够启动起来,就证明OK了。
2.2.2 配置MyBatis的XML保存路径(MyBatis运行时使用)
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
classpath:就是指当前系统运行的根路径
所有以Mapper.xml结尾的文件都是MyBatis的匹配文件,并且放在mapper这个文件夹下。如果不写这个,就无法将Interface和xml关联起来。因为xml有多个。
为什么使用xml?
因为需要写sql,放在类中写不好实现。xml更适合一点。
2.2.3 总的配置信息
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false
username: root
password: 1111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
2.3 写代码,执行MyBatis的业务处理
2.3.1 查询数据库的示例
以根据ID查询用户为例。
step1:先建立一个model包,里面创建一个User类,类中的属性和数据库中user表的信息一致,生成属性的get和set方法。
数据库中的user表:
package com.example.demo.model;
import lombok.Data;
@Data
public class User {
private int userID;
private String userName;
private String password;
}
(step2-step3都是根据业务流程写的)
step2:建立一个包controller,创建一个类Controller,用于校验参数是否合理,参数合理则调用service包中里面的方法
package com.example.demo.controller;
import com.example.demo.model.UserInfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/getuser")
public User getUserById(Integer id){
User user = null;
if(id > 0 && id != null){
user = userService.getUserById(id);
}
return user;
}
}
step3:建立一个包service,里面新建一个类UserService。UserService类里面调用userMapper的getUserById方法。
package com.example.demo.service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.User;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public User getUserById(Integer id){
return userMapper.getUserById(id);
}
}
step4:建立一个包mapper,创建一个接口UserMapper,写一个getUserById的方法。这里只是定义,具体的实现在xml中。
package com.example.demo.mapper;
import com.example.demo.model.User;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper{
public User getUserById(Integer id);
}
step5:写接口的具体实现。在resources下建立一个文件夹mapper,里面创建一个UserMapper的xml文件。
问什么要建立一个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">
<!-- namespace=你要实现的接口的完整包名+接口名 -->
<mapper namespace="com.example.demo.mapper.UserMapper">
<!-- id = 实现接口中的方法名 resultType = 返回的类型-->
<select id="getUserById" resultType="com.example.demo.model.UserInfo">
select * from user where userID = #{id}
</select>
</mapper>
<mapper></mapper>是程序员自定义的东西。
2.3.2 单元测试
SpringBoot默认就会添加一个单元测试的框架,因此无需添加单元测试框架。
点击之后就会生成一个单元测试类。在生成的单元测试类中需要干三件事情:
step1:加注解@SpringBootTest,表明当前单元测试的框架是SpringBoot,不是其他
step2:将需要测试的类引入
step3:在testGetUserById()方法中调用自己要测试的方法,然后,运行testGetUserById()方法程序就可以运行起来了。
package com.example.demo.mapper;
import com.example.demo.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
@SpringBootTest // 表示当前单元测试的框架是SpringBoot,不是其他
class UserMapperTest {
// 测试的是User Mapper的getUserById方法,需要先将类注入
@Resource
private UserMapper userMapper;
// 这个就相当于一个main方法,点击这个就可以进行测试了。由于测试的是UserMapper的方法,因此,需要先将该类引入
@Test
void testGetUserById() {
User user = userMapper.getUserById(2);
System.out.println("测试结果"+user.toString());
}
}
采用单元测试,就可以不用通过前端来验证后端写的代码是否正确,可以使用单元测试来进行验证。
三、实现数据库的增、删、改操作的示例
3.1 新增一条记录
往数据库中新增一条记录的返回结果有两种。第一种是返回受影响的行数,第二种是返回添加成功记录的主键。
3.1.1返回受影响行数的实现步骤
step1:在UserMapper接口中声明一个方法
/*
添加用户,有两种情况,第一种是返回受影响的行数、第二种是添加成功记录的主键
*/
public int addUser(String userName,String password);
step2:在UserMapper.xml中实现接口中定义的方法
<insert id="addUser">
insert into user(userName,password) values(#{userName},#{password})
</insert>
step3:进行单元测试
@Test
void addUser() {
int a = userMapper.addUser("wangwu","22222");
System.out.println(a);
}
运行结束后,查看数据库,
可以得出该方法正确。
3.1.2 返回添加成功记录的主键的实现步骤
step1:在UserMapper接口中声明一个方法
/*
添加用户,返回添加成功记录的主键,如果用户的属性很多,函数的参数可以直接写一个对象
*/
public int addUser2(User user);
step2:在UserMapper.xml中实现接口中定义的方法
useGeneratedKeys=true,表示开启自增主键,keyProperty指定自增的是哪个属性(程序的类中),keyColumn指明的数据库中表的属性
<!-- useGeneratedKeys=true,表示开启自增主键,keyProperty指定自增的是哪个属性,keyColumn指明的数据库中表的属性-->
<insert id="addUser2" useGeneratedKeys="true" keyProperty="userID" keyColumn="userID">
<!-- 传递过来的是个对象,可以直接写对象的属性-->
insert into user(userName,password) values(#{userName},#{password})
</insert>
step3:进行单元测试
@Test
void addUser2() {
User user = new User();
user.setUserName("xiaohong");
user.setPassword("11111");
int a = userMapper.addUser2(user);
//Mybatis框架已经将id设置到user中
System.out.println(user.getUserID());
}
运行结束后,查看输出的UserID以及查看数据库
使用原生的Ajax,浏览器版本不同,写法会有差异。jQuery已经做了底层的兼容。
3.2 修改一条记录
step1:在UserMapper接口中声明一个方法
public int update(User user);
step2:在UserMapper.xml中实现接口中定义的方法
<update id="update">
update user set userName=#{userName},password=#{password} where userID=#{userID}
</update>
step3:进行单元测试
@Test
void update() {
User user = new User();
user.setUserID(5);
user.setUserName("asdf");
user.setPassword("11111");
int a = userMapper.update(user);
System.out.println(a);
}
运行结束后,查看数据库
3.3 删除一条记录
step1:在UserMapper接口中声明一个方法
public int delete(int id);
step2:在UserMapper.xml中实现接口中定义的方法
<delete id="delete">
delete from user where userID=#{userID}
</delete>
step3:进行单元测试
@Test
void delete() {
userMapper.delete(2);
}
运行结束后,查看数据库
四、实现数据库的查询操作的示例
4.1 单表查询
step1:在UserMapper接口中声明一个方法
public User getUserById(Integer id);
step2:在UserMapper.xml中实现接口中定义的方法
<select id="getUserById" resultType="com.example.demo.model.User">
select * from user where userID = #{id}
</select>
4.1.1 $和#的区别
#是占位符,$是替换符,#和$的区别:
①传入的参数是int,没有影响。
step1:在UserMapper接口中声明一个方法
public User getUserById(Integer id);
step2:在UserMapper.xml中实现接口中定义的方法
<select id="getUserById" resultType="com.example.demo.model.User">
select * from user where userID = #{id}
</select>
<select id="getUserById" resultType="com.example.demo.model.User">
select * from user where userID = ${id}
</select>
在xml中实现接口的方式有两种,一种是使用#{id},另一种是使用${id}。如果传入的参数是int,这两种都可以。
②传入的参数是String,$会有影响,因为直接替换,传入的String就没有带引号
step1:在UserMapper接口中声明一个方法
public User selectByName(String userName);
step2:在UserMapper.xml中实现接口中定义的方法
<select id="selectByName" resultType="com.example.demo.model.User">
select * from user where userName=#{userName}
</select>
<select id="selectByName" resultType="com.example.demo.model.User">
select * from user where userName=${userName}
</select>
如果使用$,$是将参数直接替换,构造的sql语句是
aaaa没有加双引号,语句错误。
③传入的参数是desc或者asc,$不会影响,#会有影响,加了引号后会识别不出来
step1:在UserMapper接口中声明一个方法
public List<User> orderResult(String order);
step2:在UserMapper.xml中实现接口中定义的方法
<select id="orderResult" resultType="com.example.demo.model.User">
select * from user order by userID ${order}
</select>
如果使用#,desc会变成“desc”,sql就无法识别出这个关键字。
④使用$,会存在sql注入问题。
查询字符串也想使用$,可以加上双引号。
<select id="selectByName" resultType="com.example.demo.model.User">
select * from user where userName='${userName}'
</select>
但是这样会存在sql注入问题。以用户登录代码为例,理想情况下:当sql 注入代码:“' or 1='1”,
结论:用于查询的字段,尽量使用 #{} 查询
4.1.2 like查询(concat()函数)
like查询,构造的sql语句是:
select * from user where userName like '%ing%';
在UserMapper.xml中写的sql语句,如果使用#占位,构造的sql语句是:
select * from user where userName like '%#{username}%'
为了更方便的查看构造出的sql语句以及过滤掉springboot的启动日志,加入以下配置:
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/blogsystem?characterEncoding=utf8&useSSL=false
username: root
password: 1111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration:
# 将拼装好的sql打印出来
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
logging:
level:
root: warn
com:
example:
demo: debug
进行单元测试,观察日志
我们可以看到,使用#占位,并不能将参数传递进来。
同时,为了避免SQL注入问题,也不能使用$。我们该怎么办呢?
可以使用mysql的内置函数concat()来处理,代码如下:
public User getListByName(String partname);
<select id="getListByName" resultType="com.example.demo.model.User">
select * from user where userName like concat('%',#{partname},'%')
<!-- select * from user where userName like concat('%','x','%')-->
</select>
该方法的单元测试就不做赘述啦。
4.2 多表查询
4.2.1 返回字典映射:resultMap
字段名称和程序中的属性名不同的情况,可以使用resultMap配置映射。
一对一和一对多可以使用resultMap映射并查询数据。
4.2.2 一对一的表映射<association>
一篇博客只对应一个用户为例。
step1:在Blog类中加上user属性。
package com.example.demo.model;
import lombok.Data;
@Data
public class Blog {
private int blogId;
private String blogTitle;
private String blogContent;
private int authorID;
private User user;
}
step2:构建BlogMapper接口,并写一个根据博客id查询博客信息的方法
package com.example.demo.mapper;
import com.example.demo.model.Blog;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface BlogMapper {
public Blog getBlogByID(int blogID);
}
step3:新建一个文件BlogMapper.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">
<!--namespace=要实现的接口的完整包名和类名-->
<mapper namespace="com.example.demo.mapper.BlogMapper">
<!-- id 起到一个标识的作用 type=要映射的类-->
<resultMap id="BashMap" type="com.example.demo.model.Blog">
<!-- prpperty:类的属性 column:在表中的属性-->
<id property="blogId" column="blogId"></id>
<result property="blogContent" column="blogContent"></result>
<result property="blogTitle" column="blogTitle"></result>
<result property="authorID" column="userID"></result>
<!-- 需要创建用户的resultMap-->
<association property="user"
resultMap="com.example.demo.mapper.UserMapper.UserMap"
columnPrefix="u_"></association>
</resultMap>
<select id="getBlogByID" resultMap="BashMap">
select b.*,u.userID u_userID,u.userName u_userName from blog b left join user u on b.authorID=u.userID where b.blogID=#{blogID}
</select>
</mapper>
step4:由于在step3中,需要用到User类的映射,接下来,在UserMapper.xml中写user的映射
<resultMap id="UserMap" type="com.example.demo.model.User">
<id property="userID" column="userID"></id>
<result property="userName" column="userName"></result>
<result property="password" column="password"></result>
</resultMap>
step5:进行单元测试,在此不做赘述。
4.2.3 一对多的表映射
一个用户可以写多篇博客。
step1:在User类中加上List<Blog>属性。
package com.example.demo.model;
import lombok.Data;
import java.util.List;
@Data
public class User {
private int userID;
private String userName;
private String password;
private List<Blog> blogList;
}
step2:在UserMapper接口,并写一个根据用户id查询用户所有博客的方法
public User getUserByID2(int userID);
step3:在UserMapper.xml的文件。
<resultMap id="UserMap" type="com.example.demo.model.User">
<id property="userID" column="userID"></id>
<result property="userName" column="userName"></result>
<result property="password" column="password"></result>
<collection property="blogList"
resultMap="com.example.demo.mapper.BlogMapper.BashMap"
columnPrefix="b_">
</collection>
</resultMap>
<select id="getUserByID2" resultMap="UserMap">
select u.*,b.blogID b_blogID,b.blogTitle b_blogTitle,b.blogContent b_blogContent,b.authorID b_authorID from user u left join blog b on u.userID=b.authorID where
u.userID=#{userID}
</select>
step4:进行单元测试,在此不做赘述。
五、动态SQL
5.1 if标签
在注册时,有些字段是必须填的,如用户名、密码等。有些字段不是必须填的,比如爱好、邮箱、年龄等。因此在注册的时候,有的用户提交的信息是不确定的,有的用户提交的字段是用户名密码和爱好,有的提交的是用户密码邮箱等等,字段的排列组合很多,如何针对每一种组合写一个方法,就太麻烦了。这时,可以借助if标签。
text中的blogContent,是传入对象中的属性,不是数据库字段
public int addBlog(String blogTitle,String blogContent,int authorID);
<insert id="addBlog">
insert into blog(blogTitle,
<if test="blogContent != null">
blogContent,
</if>
authorID
) values(
#{blogTitle},
<if test="blogContent">
#{blogContent},
</if>
#{authorID})
</insert>
使用单元测试:
但是,if也有一些缺点,比如,
<insert id="addBlog">
insert into blog(blogTitle,
<if test="blogContent != null">
blogContent,
</if>
<if test="authorID = 0">
authorID,
</if>
) values(
#{blogTitle},
<if test="blogContent">
#{blogContent},
</if>
<if test="authorID">
#{authorID},
</if>
)
</insert>
如果只传了一个参数,会导致构造的sql多了一个逗号,导致sql语句有问题,程序报异常。使用trim标签可以帮我们解决这个多个符号或者少个符号的问题。
5.2 trim标签
<trim>标签搭配if标签,可以实现对多个字段动态查询。<trim>标签有如下属性:
prefix:表示整个语句块,以prefix的值作为前缀
suffix:表示整个语句块,以suffix的值作为后缀
prefixOverrides:表示整个语句块要去除掉的前缀
suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="addBlog">
insert into blog
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="blogTitle != null">
blogTitle,
</if>
<if test="blogContent != null">
blogContent,
</if>
<if test="authorID != 0">
authorID,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="blogTitle != null">
#{blogTitle},
</if>
<if test="blogContent">
#{blogContent},
</if>
<if test="authorID">
#{authorID},
</if>
</trim>
</insert>
5.3 where标签
传入的用户对象,根据属性实现where条件查询,作为查询依据的属性可能有多个。
public List<Blog> selectByInfo(Blog blog);
<select id="selectByInfo" resultType="com.example.demo.model.Blog">
select blogId,blogTitle,blogContent,authorID from blog
<where>
<if test="blogId != null">
and blogId=#{blogId}
</if>
<if test="blogTitle != null">
and blogTitle=#{blogTitle}
</if>
<if test="blogContent != null">
and blogContent=#{blogContent}
</if>
<if test="authorID != 0">
and authorID=#{authorID}
</if>
</where>
</select>
单元测试:
@Test
void selectByInfo() {
Blog blog = new Blog();
blog.setBlogId(1);
List<Blog> blogs = blogMapper.selectByInfo(blog);
System.out.println(blogs);
}
where标签会自动去掉结尾的and。
5.4 set标签
根据传入的属性动态更新数据库中的记录。
public int updateBlog(Blog blog);
<update id="updateBlog">
update blog
<set>
<if test="blogId != null">
blogId=#{blogId},
</if>
<if test="blogTitle != null">
blogTitle=#{blogTitle},
</if>
<if test="blogContent != null">
blogContent=#{blogContent},
</if>
<if test="authorID != 0">
authorID=#{authorID},
</if>
</set>
where blogId=#{blogId}
</update>
单元测试:
@Test
void updateBlog() {
Blog blog = new Blog();
blog.setBlogId(1);
blog.setBlogTitle("titlr11");
blog.setBlogContent("content11");
blogMapper.updateBlog(blog);
}
5.5 foreach标签
在遍历集合时,可以使用该标签,他有以下属性:
collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
item:遍历时的每一个对象
open:语句块开头的字符串
close:语句块结束的字符串
separator:每次遍历之间间隔的字符串
举个例子:根据多个博客id删除博客
public int deleteBlogByIDs(int[] IDs);
<delete id="deleteBlogByIDs">
delete from blog
where blogId in
<foreach collection="IDs" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</delete>
单元测试:
@Test
void deleteBlogByIDs() {
int[] IDs = {1, 2, 3};
int a = blogMapper.deleteBlogByIDs(IDs);
System.out.println(a);
}
六、总结
MyBatis是一种更简单实现程序和数据库交互的工具,通过学习MyBatis,主要了解了以下知识:
①配置一个MyBatis的开发环境。MyBatisFramework和MySQLDriver这两个缺一不可。在配置数据库的连接信息时,除了URL、用户名和密码外,还需要配置driver-class-name,指明数据库的类型。MyBatis的xml文件是增删改查方法的具体实现,需要配置xml文件的保存路径。
②MyBatis具体的业务处理。总体流程时:建立实体类,属性和数据库表中表保持一致——》建立XXXMapper接口,并在类中定义一个想要实现的方法——》在resource文件夹下建立一个文件mapper,创建XXXMapper的xml文件。在xml文件中实现刚刚接口中定义的方法——》单元测试
③增加分为返回受影响的行数和返回添加成功记录的主键这两种。第二种需要用到useGeneratedKeys和keyProperty的属性。
④ #和$的区别。传入的参数是String,对$有影响。如果传入的参数sql关键字,如desc,会对#有影响。$还存在sql注入问题。
⑤学习了like查询,需要使用mysql的内置函数concat()。需要先映射
⑥学习了多表查询。先建立实体类的映射,映射时用到resultMap标签。一对一用到的是association标签,一对多用的是collection标签。
⑦学习了动态SQL的查询。学习了<if>、<trim>、<where>、<set>和<foreach>。