MyBatis是一个框架,是程序和真正的数据库之间的桥梁,MyBatis是居于ORM框架,ORM是object Relational Mapping 对象关系映射
也就是说,MyBatis是和程序中的类相对应的
类就是MyBatis中的表
一个对象就是MyBatis中的一条记录(一行)
属性就是MyBatis中的字段
所以我们操作java中的对象的时候,同时也在操作MyBatis中的数据
第一步就是添加依赖,**同时要添加MyBatis和Mysql的依赖,**因为MyBatis只是一个框架,一个平台,真正底层实现的还是Mysql
如果是新创建项目,直接添加就可以了
如果是老项目,使用EditStarter就可以添加上面两个依赖
虽然我们添加了两个依赖,一个是MySQL,一个是MyBatis,但是我们还没有进行连接.
MySQL连接:
在application-dev.yml中设置连接
spring:
datasource:
url: jdbc:mysql://localhost:3306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
driver-class-name 如果是5.x以后的才可以设置为com.mysql.cj.jdnc.Driver
MyBatis配置:
现在resource文件夹下面创建一个文件夹,之后这个里面存放的都是mybatis的文件
然后在主配置文件中设置一下mybatis的配置路径
# application.yml:
# 配置mybatis的保存路径
mybatis:
mapper-locations: classpath:mybatis/**Mapper.xml
-- 创建数据库
drop database if exists mycnblog;
create database mycnblog DEFAULT CHARACTER SET utf8mb4;
-- 使用数据数据
use mycnblog;
-- 创建表[用户表]
drop table if exists userinfo;
create table userinfo(
id int primary key auto_increment,
username varchar(100) not null,
password varchar(32) not null,
photo varchar(500) default '',
createtime datetime default now(),
updatetime datetime default now(),
`state` int default 1
) default charset 'utf8mb4';
-- 创建文章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null,
content text not null,
createtime datetime default now(),
updatetime datetime default now(),
uid int not null,
rcount int not null default 1,
`state` int default 1
)default charset 'utf8mb4';
-- 创建视频表
drop table if exists videoinfo;
create table videoinfo(
vid int primary key,
`title` varchar(250),
`url` varchar(1000),
createtime datetime default now(),
updatetime datetime default now(),
uid int
)default charset 'utf8mb4';
-- 添加一个用户信息
INSERT INTO `mycnblog`.`userinfo` (`id`, `username`, `password`, `photo`, `createtime`, `updatetime`, `state`) VALUES
(1, 'admin', 'admin', '', '2021-12-06 17:10:48', '2021-12-06 17:10:48', 1);
-- 文章添加测试数据
insert into articleinfo(title,content,uid)
values('Java','Java正文',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://www.baidu.com',1);
总体执行过程:
创建一个Model包,里面创建一个UserInfo类
这个类里面的属性和我们之前创建的表中的属性相同
package com.example.demo.Model;
import lombok.Data;
@Data
public class UserInfo {
private int id;
private String username;
private String password ;
private String photo ;
private String createtime ;
private String updatetime;
private int state;
}
要使用MyBatis框架的话,主要有一下两个操作,一个是有接口方法,一个是使用.xml的接口方法的实现.
只有这两个同时使用才可以完成对数据库的操作
创建一个Mapper包,里面放的都是和上面实体类对应的接口类
每一个接口类中都是一个个方法,但是这个只是一个声明,声明这个动作.真正的实现要到.xml中使用sql实现
要记住加上啊Mapper注解
@Mapper
public interface userMapper {
//可以视为传递给数据库的名字
public UserInfo getUserById(@Param("id") Integer id);
}
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="getUserById" resultType="com.example.demo.Model.UserInfo">
select * from userinfo where id=${id}
select>
mapper>
打印真正的SQL语句,在application-dev.yml中进行配置
# 开启 MyBatis SQL 打印
# 要先将这个日志级别设置好为debug类型的
logging:
level:
com:
example:
demo: debug
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置完成之后我们就可以看到控制台上面的完整的SQL语句
==> Preparing: select * from userinfo where id=1
==> Parameters:
<== Columns: id, username, password, photo, createtime, updatetime, state
<== Row: 1, admin, admin, , 2021-12-06 17:10:48, 2021-12-06 17:10:48, 1
<== Total: 1
上面我们完成了Mybatis的编写,但是还需要Service层,Controller层,打开浏览器发送请求,这样的测试过程是麻烦的
所以为了方便我们进行验证,我们就可以使用单元测试进行模拟一下
在我们创建SpringBoot的时候,就已经内置了这个测试框架了,这个框架底层是通过JUnit实现的
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
哪一个类都可以生成关于它的单元测试的类,只要有了框架,我们就可以对一个类进行测试.
配置完成之后,我们就会发现,idea就会在test的文件夹下面生成我们刚才配置的类.
注意是在test绿色文件夹下面生成的:
1.给单元测试类添加注解@SpringBootTest表示当前单元测试运行在SpringBoot的环境中
2. 编写测试代码
package com.example.demo.Mapper;
import com.example.demo.Model.UserInfo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import javax.annotation.Resource;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserMapperTest {
@Resource
UserMapper userMapper;
@Test
void getUserById() {
UserInfo userInfo=userMapper.getUserById(1);
System.out.println(userInfo);
}
}
直接使用方法旁边的Run按钮就可以直接对这一个方法进行测试了
UserInfo(id=1, username=admin, password=admin, photo=, createtime=2021-12-06 17:10:48, updatetime=2021-12-06 17:10:48, state=1)
判断我们的测试结果是否成功
![[Pasted image 20220727135738.png]]
@Test
void getUserById() {
UserInfo userInfo=userMapper.getUserById(2);
//断言,测试
Assertions.assertNotNull(userInfo);
}
//根据用户id更改用户信息 将参数都加上@param注解更加稳妥,不易报错
public int updateUserName(@Param("id") Integer id,
@Param("username") String username);
这个#里面的名字和上面Param中的字符串对应
<update id="updateUserName">
update userinfo set username=#{username} where id=#{id}
update>
@Test
void updateUserName() {
int length=userMapper.updateUserName(1,"lxylxy");
Assertions.assertEquals(1,length);
}
==> Preparing: update userinfo set username=? where id=?
==> Parameters: lxylxy(String), 1(Integer)
<== Updates: 1
成功执行
//更加用户id删除用户
public int deleteUser(@Param("id") Integer id);
<delete id="deleteUser">
delete from userinfo where id=#{id}
delete>
@Test
@Transactional//加上这个就不会真正的删除,不会污染数据库
void deleteUser() {
int length=userMapper.deleteUser(1);
Assertions.assertEquals(1,length);
}
//添加用户
public Integer addUser(UserInfo userInfo);
这个添加方法的sql有一点难度,还是使用#{},{}直接写对象的属性就可以
<insert id="addUser">
insert into userinfo (username,password,photo,state) values
(#{username},#{password},#{photo},1)
insert>
下面写一个返回id的版本的添加数据,就是在insert标签上面添加多个属性
userGenerateKeys为true表示可以为获取到主键
keyProperties表示要返回的键值的名字
<insert id="addUser2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into userinfo (username,password,photo,state) values
(#{username},#{password},#{photo},1)
insert>
@Test
void addUser() {
int lenght=userMapper.addUser(new UserInfo("12","111",""));
Assertions.assertEquals(1,lenght);
}
${}
直接执行的方式,所以,对于字符串类型的元素,${}
是没有引号标记的,所以它只可以传递int类型的数据,但是#{}
可以传递任意数据#{}
会进行提取,所以它更加安全,而${}
可能会传入各式各样的sql语句,不安全但是,当我们传输的内容是一个sql的关键字如desc/asc的时候,我们就要使用${}
了,因为它会直接将关键字拿过来,这个时候我们在业务代码那里就要加上验证,防止sql注入的问题
但是对于#{}
来说,它会以为我们传递的是一个value,是一个值,还会给它加上引号’’
使用${}
select * from userinfo order by createtime desc
使用#{}
select * from userinfo order by createtime 'desc'
因为我们之前发现${}只会简单的将数据拷贝,不会加上引号,所以我们可以在它的两边加上’'试试.
但是这样依旧还是有问题的,如果我们输入的字符串是' or 1='1
的话,就会发生错误,原因是:
select * from userinfo where username='lxylxy' and password='' or 1='1'
我们发现最后生成的sql语句竟然是这样的,这样的话就会造成数据泄露了.
所以,我们最好还是使用#{}
结果是:
==> Preparing: select * from userinfo where username=? and password=?
==> Parameters: lxylxy(String), ' or 1='1(String)
<== Total: 0
所以,我们最好还是使用#{}
我们有的时候会进行模糊查询,比如说用户想要查找用户姓名中含有1的用户,这个输入输入1,就要求打印出来.
这个时候,sql语句我们希望是%1
我们如果使用#{}来进行like查询的时候.
select * from userinfo where username like '%#{username}%'
最后的结果是:
select * from userinfo where username like '%'1'%'
是达不到要查询的效果的
所以但是我们使用${}也是不太好的,因为会有sql注入的风险
所以,这个时候我们可以使用mysql提供的一个方法concat进行字符串之间的拼接,这样就可以消除引号的影响了
select * from userinfo where username like concat('%',#{username},'%')
之前我们进行查询语句的时候,都是使用resultType进行接收的,但是下面有两种时候,我们需要使用resultMap进行接收
当数据库的字段和程序的属性不同的时候,就一定得使用resultMap进行接收了,使用resultType是接收不到的
# 映射的名字,对应的类的名字
<resultMap id="baseMap" type="com.example.demo.Model.UserInfo">
# 主键映射
<id column="id" property="id">id>
# 非主键映射
<result column="username" property="name">result>
<result column="password" property="pwd">result>
resultMap>
里面填写的就是上面的resultMap的名字
<select id="getUserById" resultMap="baseMap">
select * from userinfo where id=#{id}
select>
另外,还记得添加元素返回主键的那个sql语句吗.
我们传入的是这个用户类,传入的是程序中类的类型,所以如果程序中类的名字和属性值不一样的话,这个sql中的名字也要变为和程序中的类的属性相同名
<insert id="addUser2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into userinfo (username,password,photo,state) values
(#{name},#{pwd},#{photo},1)
</insert>
当单表查询的时候,我们也是要使用到resultMap的
在ArticleInfo类中,如果我们加入UserInfo属性,这个属性在数据库中没有,但是我们在类中进行添加了
package com.example.demo.Model;
import lombok.Data;
@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; //新加入的属性
}
如果我们想要通过文章的id,获取ArticleInfo的话
ArticleMapper的方法:
package com.example.demo.Mapper;
import com.example.demo.Model.ArticleInfo;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ArticleMapper {
public ArticleInfo getArticleById(Integer id);
}
那么我们在ArticleMapper的xml文件中就要注意了,
因为我们的ArticleInfo是包含了UserInfo的
sql语句:
select a.*,u.id u_id,u.username u_username,u.password u_password from
articleinfo a left join userinfo u on a.uid=u.id where u.id =1
这个大体上是搜索了ArticleInfo的全部信息和UserInfo的id,username,password
还给ArticleInfo起别名为a,UserInfo的别名是u
但是因为是select语句,所以要带有返回值,因为返回值中多了一个UserInfo.所以我们需要新建立一个ResultMap
<resultMap id="BaseMap" type="com.example.demo.Model.ArticleInfo">
<id column="id" property="id">id>
<result column="title" property="title">result>
<result column="content" property="content">result>
<result column="createtime" property="createtime">result>
<result column="uid" property="uid">result>
<result column="rcount" property="rcount">result>
<result column="state" property="state">result>
<association property="userInfo"
resultMap="com.example.demo.Mapper.UserMapper.baseMap"
columnPrefix="u_">
association>
resultMap>
一个用户对应多篇文章
这个和一对一十分的相似,只是对应了多篇文章了
UserInfo类:
我们注意到最后的属性,List
@Data
@AllArgsConstructor
public class UserInfo {
private int id;
private String name;
// private String password ;
private String pwd ;
private String photo ;
private String createtime ;
private String updatetime;
private int state;
private List<ArticleInfo> articleInfoList;
}
UserMapper.xml:
对于UserInfo的ResultMap:
<resultMap id="baseMap" type="com.example.demo.Model.UserInfo">
<id column="id" property="id">id>
<result column="username" property="name">result>
<result column="password" property="pwd">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>
<collection property="articleInfoList"
resultMap="com.example.demo.Mapper.ArticleMapper.BaseMap"
columnPrefix="a_"
>
collection>
resultMap>
<select id="getUserAndArticleById" resultMap="baseMap">
select u.*,a.id a_id,a.title a_title,a.content a_content,
a.createtime a_createtime,a.updatetime a_updatetime
from userinfo u left join articleinfo a on a.uid=u.id where u.id=#{id}
select>
当某些参数,可传递,可不传递的时候,我们就可以使用if标签来修饰这个属性
比如说userinfo中的photo属性.如果它是否传递都是可以的
那么就可以使用if标签进行标记
<!-- 添加可以不传入photo的用户信息-->
<insert id="addUser3">
insert into userinfo (
username
,password
<if test="photo!=null">
,photo
</if>
) values(
#{name},#{pwd}
<if test="photo!=null">
,#{photo}
</if>
)
</insert>
使用if标签进行拼接
当有多个可以不传入的参数,但是至少有一个会被传入时,我们就可以使用trim和if标签组合起来使用
trim标签主要用户拼接前后缀和删除指定的前后字符
<!-- 使用trim进行拼接和裁剪的-->
<insert id="addUser4">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null">
username,
</if>
<if test="pwd!=null">
password,
</if>
<if test="photo!=null">
photo,
</if>
</trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="name!=null">
#{name},
</if>
<if test="pwd!=null">
#{pwd},
</if>
<if test="photo!=null">
#{photo},
</if>
</trim>
</insert>
当使用where查询时,查询的条件是不确定的,有的参数可能传入,有的参数可能不传入.也就是不确定的条件下
我们就可以使用where标签来代替常规的where sql语句
主要作用:
<select id="getUserByNameAndPwd" resultMap="baseMap">
select * from userinfo
<where>
<if test="name!=null">
and username=#{name}
</if>
<if test="pwd!=null">
and password=#{pwd}
</if>
</where>
</select>
当不传入name属性时:
会不拼接name,只拼接password,而且还会去掉password前面的and
select * from userinfo WHERE password=?
where相当于trim的prefix是where,prefixoverride是and
用于修改字段的值
update userinfo set username =‘xxx’ where id=1;
这个动态的set就是为了解决传入的内容不确定的问题的
<update id="updateUserInfo">
update userinfo
<set>
<if test="name!=null">
username=#{name},
</if>
<if test="pwd!=null">
password=#{pwd},
</if>
</set>
where id=#{id}
</update>
当我们只传入了name的时候sql语句是这样的:
update userinfo SET username=? where id=?
相当于trim的prefix=set,suffixoverrides=‘,’
当要删除多个值的时候,就可以使用foreach来进行遍历
foreach标签的常用属性
<delete id="deleteListUser">
delete from userinfo where id in
<foreach collection="list" open="(" close=")" item="id" separator=",">
#{id} //要和item里面的对应
</foreach>
</delete>
Preparing: delete from userinfo where id in ( ? , ? , ? )
==> Parameters: 15(Integer), 18(Integer), 17(Integer)