目录
1.Invalid bound statement (not found)
3. Failed to load ApplicationContext
一.Mybatis
1.什么是Mybatis
2.优化JDBC流程
后端开发的两个重要组成部分:①后端程序 ②数据库
而Mybatis就是优化后端程序与数据库交互的框架
之前我们使用JDBC与数据库进行交互的.
回顾JDBC流程
接来下学习MyBatis框架
二.Mybatis框架
1.建立数据库
-- 创建数据库
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';
-- 添加⼀个⽤户信息
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)
;
2.添加Mybatis支持
之后pom文件的依赖有如下
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter-test</artifactId>
<version>2.3.1</version>
<scope>test</scope>
</dependency>
</dependencies>
3.配置application.yml文件
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://localhost:13306/mycnblog?characterEncoding=utf8&useSSL=false
username: root
password: woaini520
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis xml 配置路径
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
configuration: # 配置打印 MyBatis 执行的 SQL
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
在配置文件中配置configuration.log-iml内容,可以打印sql执行的细节
4.创建实体类
package com.javastudy.mybatisdemo4.model;
import lombok.Data;
import java.sql.Date;
/**
* @author Chooker
* @create 2023-07-25 14:03
*/
@Data
public class User {
private Integer id;
private String username;
private String password;
private String photo;
private Date createtime;
private Date updatetime;
}
5.创建mapper接口
package com.javastudy.mybatisdemo4.mapper;
import com.javastudy.mybatisdemo4.model.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author Chooker
* @create 2023-07-25 14:02
*/
@Mapper
public interface UserMapper {
List<User> selectAll();
}
6.创建mapper.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">
<mapper namespace="com.javastudy.mybatisdemo4.mapper.UserMapper">
<select id="selectAll" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo;
</select>
</mapper>
7.进行单元测试
package com.javastudy.mybatisdemo4.mapper;
import com.javastudy.mybatisdemo4.model.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
/**
* @author Chooker
* @create 2023-07-25 14:12
*/
@Slf4j
@SpringBootTest
class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
void selectAll() {
List<User> users = userMapper.selectAll();
log.info(users.toString() );
}
}
8.可能发生的错误
1.Invalid bound statement (not found)
定义的mapper映射和接口定义的方法名不一致.
mybatis配置路径错误
2.数据库相关的配置错误
可能是数据库账号或者密码出现错误,也可能是数据库相关的配置出现错误
3. Failed to load ApplicationContext
类不存在,检查路径是否出现错误
4.yml格式错误
三.传参的方式
1.一个参数
@Mapper
public interface UserMapper {
User selectById(Integer id);
}
<select id="selectById" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where id= #{id};
</select>
这个时候我们可以直接传参数,并且当 User selectById(Integer aaa);参数与xml文件里不一致的时候,也是可以查到相应数据的.
当我们使用参数进行重命名的时候
User selectById(@Param("uid") Integer id);
此时xml文件里面必须为uid,否则就会报如下错误
此时只需要改为uid即可
2.多个参数
多个参数的时候必须一致,不然可能会报错
四.增删改查
1.插入数据
Integer insert(User user);
<insert id="insert">
insert into userinfo(username,password,photo) values(#{username},#{password},#{photo})
</insert>
可以看到此时是成功插入数据了的
如果使用@Param注解,需要这样进行修改
Integer insert2(@Param("userInfo") User user);
<select id="insert2">
insert into userinfo(username,password,photo) values(#{userInfo.username},#{userInfo.password},#{userInfo.photo})
</select>
总结:如果设置了@Param注解,就要使用@Param注解里的参数
这样就不会报binding error
当我们需要拿到自增的id的时候,我们可以采取如下的方式
<insert id="insert2" useGeneratedKeys="true" keyProperty="id">
insert into userinfo(username,password,photo) values(#{userInfo.username},#{userInfo.password},#{userInfo.photo})
</insert>
@Test
void insert() {
User user = new User();
user.setUsername("ljl");
user.setPassword("147258");
user.setPhoto("123abc");
Integer result = userMapper.insert2(user);
log.info("影响了" + result + "行"+",userId="+user.getId());
}
通过user.getId()即可拿到自增的id.
2.修改数据
void update(User user);
<update id="update">
update userinfo set username=#{username},password=#{password},id=#{id} where id=#{id}
</update>
可以观察到修改成功
3.删除数据
Integer deleteById(Integer id);
<delete id="deleteById">
delete from userinfo where id=#{id}
</delete>
可以观察到删除成功
4.查找数据
List<User> selectAll();
<select id="selectAll" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo;
</select>
上面都有具体的代码,这里不做过多的演示
五.$和#之间的区别
1.$替换Interger类型的sql语句
<select id="selectById" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where id= ${uid};
</select>
是可以正常查询到数据的
2.$替换String类型的sql语句
<select id="selectByName" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username= ${username};
</select>
@Test
void selectByName() {
User user = userMapper.selectByName("xcl");
log.info(user.toString());
}
此时会发生一定的问题
可以看到username=xcl并没有给后面的内容加引号,因此我们可以进行相应的修改可以符合条件
<select id="selectByName" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username= '${username}';
</select>
此时查询是没有问题的
3.SQL注入的问题
使用$可能会发生SQL注入的情况
什么是SQL注入?下面拿用户登录的例子来演示以下
正常的情况:使用#
UserMapper接口
User selectByNameAndPassword(String username,String password);
UserMapper.xml文件
<select id="selectByNameAndPassword" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username= #{username} and password=#{password};
</select>
测试方法: 当正确输入账号和密码,这个时候是登陆成功的
@Test
void selectByNameAndPassword() {
String username="xcl";
String password="147258";
User user = userMapper.selectByNameAndPassword(username, password);
if (user == null) {
log.info("登陆失败");
} else {
log.info("登陆成功:" + user.toString());
}
}
当SQL注入错误输入密码时:登陆失败,符合预期
@Test
void selectByNameAndPassword() {
String username="xcl";
String password=" or 1=1";
User user = userMapper.selectByNameAndPassword(username, password);
if (user == null) {
log.info("登陆失败");
} else {
log.info("登陆成功:" + user.toString());
}
}
#的情况时用占位符的方式进行替换,预编译的方式将内容变为password的值进行替换
异常的情况:使用$
UserMapper接口
User selectByNameAndPassword(String username,String password);
UserMapper.xml文件
<select id="selectByNameAndPassword" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username= '${username}' and password= '${password}';
</select>
测试方法: 当SQL注入错误输入密码时:报错误,不符合预期
@Test
void selectByNameAndPassword() {
String username = "xcl";
String password = "' or 1='1";
User user = userMapper.selectByNameAndPassword(username, password);
if (user == null) {
log.info("登陆失败");
} else {
log.info("登陆成功:" + user.toString());
}
}
$打印的日志,可以看到是直接替换的方式进行SQL语句的查询
注意:此时数据库只有一个用户信息,这只是为了演示SQL注入的问题.
总结:对于查询的语句,尽量使用#符号
4.不得不使用$的理由
1.当我们需要按顺序查询数据的时候
List<User> selectByOrder(String order);
<select id="selectByOrder" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo order by id #{order}
</select>
@Test
void selectByOrder() {
List<User> users = userMapper.selectByOrder("DESC");
log.info(users.toString());
}
当数据类型为String的时候,自动就加引号,但是此时我们不需要引号,因此只能使用$
改为这样就正确了:
<select id="selectByOrder" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo order by id ${order}
</select>
但是$存在SQL注入的问题,我们该怎么样解决呢?
我们在购物平台的时候,都是自己点击前端提供的参数然后进行排序的,不让用户自己输入,而是前端提供几个选项,选择完成之后传入后端,让后端进行拼接,这样就不会存在sql注入的问题了.
2.like模糊查询的时候
List<User> selectByLike(String name);
<select id="selectByLike" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username like '%#{name}%'
</select>
@Test
void selectByLike() {
List<User> users = userMapper.selectByLike("l");
log.info(users.toString());
}
此时使用#就会发生上面的错误,因为我们穿的参数也是String类型的,而模糊查询里面相当于引号里面还有引号了,此时我们只能使用$进行查询
<select id="selectByLike" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username like '%${name}%'
</select>
替换为$符号就不会发生问题
但是这样之后还是会发生SQL注入的问题?该如何解决呢?
此时需要使用mysql的一个内置函数concat()
<select id="selectByLike" resultType="com.javastudy.mybatisdemo4.model.User">
select * from userinfo where username like concat('%',#{name},'%')
</select>
此时就不会发生错误
总结:
$和#的区别
- $存在SQL注入问题,原因:#预编译,$是字符直接替换
- order by只能使用$,如何避免SQL注入,后端控制参数的输入,只能为desc, asc
- #直接用于like查询会报错,需要使用mysql内置函数concat
六.返回类型
1.resultType
绝大多数的情况直接返回resultType即可,当返回的JDK中定义的数据类型,只需要指明所在的位置即可,例如
<select id="getNameById" resultType="java.lang.String">
select username from userinfo where id=#{id}
</select>
此时返回的是String类型的username,返回类型resultType即为String所在的位置.
当返回的是自定义的对象的时候,比如数据库一个表对应java中的一个自定义的对象,如果返回类型还未resultType,那么此时要求数据库表中的字段名与java自定义的对象的名称一一对应且名字相同,否则找不到相应的字段数据.之前我们都是字段名与自定义的实体类相同的,所以返回的可以使resultType.
当我们字段名与实体类的名称不一致了,会发生什么情况呢?
此时可以看到name为null,pwd为null,说明在数据库中没有找到相匹配的字段名进行赋值,其他的属性还是可以被赋值的.
这种情况该如何解决呢?接下来resultMap闪亮登场!!!
2.resultMap
<resultMap id="userMap" type="com.javastudy.mybatisdemo4.model.User">
<id property="id" column="id"></id>
<result property="name" column="username"></result>
<result property="pwd" column="password"></result>
<result property="photo" column="photo"></result>
<result property="createtime" column="createtime"></result>
<result property="updatetime" column="updatetime"></result>
</resultMap>
<select id="selectAll" resultMap="userMap">
select * from userinfo;
</select>
通过把resultType改为resultMap,并重建一个resultMap,将实体类的property(属性)和数据库中的column(字段)一一对应起来,mybatis就知道该如何进行赋值了.
其中id为主键,property为java属性字段名,colum为数据库字段名,
一般来说通常将实体类的属性一一与数据库字段名进行对应,我们定义的user实体类只有name和pwd与数据库不同,后面的属性与数据库字段名相同,所以可以不写,但在这里还是建议所有的属性与字段名都要进行联系起来,即使相同.
此时可以看到可以查询出来想要的结果了.
七.多表查询
1.准备工作
1.创建文章表
-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
id int primary key auto_increment,
title varchar(100) not null default "默认名字",
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 articleinfo(title,content,uid)
values('java','java正⽂',1);
-- 添加视频
insert into videoinfo(vid,title,url,uid) values(1,'java title','http://ww
w.baidu.com',1);
2.创建文章实体类
package com.javastudy.mybatisdemo4.model;
import lombok.Data;
import java.sql.Date;
/**
* @author Chooker
* @create 2023-07-26 10:30
*/
@Data
public class Article {
private Integer id;
private String title;
private String content;
private Date createtime;
private Date updatetime;
private Integer rcount;
private Integer state;
//作者相关信息
private User user;
}
3.创建ArticleMapper类
@Mapper
public interface ArticleMapper {
List<Article> selectAll();
}
4.创建ArticleMapper.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">
<mapper namespace="com.javastudy.mybatisdemo4.mapper.ArticleMapper">
<resultMap id="articleMap" type="com.javastudy.mybatisdemo4.model.Article">
<id property="id" column="id"></id>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="rcount" column="rcount"></result>
<result property="state" column="state"></result>
<result property="createtime" column="createtime"></result>
<result property="updatetime" column="updatetime"></result>
<association property="user" resultMap="com.javastudy.mybatisdemo4.mapper.UserMapper.userMap">
</association>
</resultMap>
<select id="selectAll" resultMap="articleMap">
select * from articleinfo t1 left join userinfo t2 on t1.uid=t2.id
</select>
</mapper>
通过 association 可以使Article实体类中的user对象与UserMap.xml中的resultMap进行关联,从而使其查询出来的内容赋值到user对象中.
5.创建测试类
/**
* @author Chooker
* @create 2023-07-26 11:46
*/
@SpringBootTest
@Slf4j
class ArticleMapperTest {
@Autowired
ArticleMapper articleMapper;
@Test
void selectAll() {
List<Article> articles = articleMapper.selectAll();
log.info(articles.toString());
}
}
但是通常我们不直接写User,而是将User中的某些属性(主键和经常使用到的属性)放在Article实体类中.
@Data
public class Article {
private Integer id;
private String title;
private String content;
private Date createtime;
private Date updatetime;
private Integer rcount;
private Integer state;
//作者相关信息
// private User user;
private Integer userId;
private String username;
}
<resultMap id="articleMap" type="com.javastudy.mybatisdemo4.model.Article">
<id property="id" column="id"></id>
<result property="title" column="title"></result>
<result property="content" column="content"></result>
<result property="rcount" column="rcount"></result>
<result property="state" column="state"></result>
<result property="createtime" column="createtime"></result>
<result property="updatetime" column="updatetime"></result>
<!-- <association property="user" resultMap="com.javastudy.mybatisdemo4.mapper.UserMapper.userMap">-->
<!-- </association>-->
<result property="userId" column="uid"></result>
<result property="username" column="username"></result>
</resultMap>
<select id="selectAll" resultMap="articleMap">
select t1.*,
t2.id as userid,t2.username as username
from articleinfo t1
left join userinfo t2 on t1.uid = t2.id
</select>
八.动态SQL的使用
1.什么是动态SQL
当我们添加用户信息的时候,有些信息是必填的,有些信息是非必填的,也就是说插入数据的时候,有些数据有时候可能是不需要插入的,如果我们使用静态SQL,我们需要写很多的SQL语句进行插入,如果我们使用动态SQL,一条SQL语句就可以针对不同的场景进行插入数据了.
动态SQL:根据输入的参数不同,动态的拼接SQL.
可能会有些人这样考虑,用户没有输入值的属性,我们就直接赋值为null不就可以了吗?但是建立表的时候,有些字段是由默认值的,因此用户没有输入值的时候应该为默认值,如果直接赋值为null,就不符合常规了.
2.if标签
之前我们创建表的时候,如果没有state的值,state默认为1.
<select id="insertByCondition">
insert into articleinfo(title,content,uid,<if test="state!=null">
,state
</if> )
values (#{title},#{content},#{userid}<if test="state!=null">
,#{state}
</if>)
</select>
当不给state赋值的时候:
@Test
void insertByCondition() {
Article article = new Article();
article.setTitle("测试文章");
article.setContent("测试文章内容");
article.setUserId(2);
Integer result = articleMapper.insertByCondition(article);
log.info("总共插入了" + result + "条数据");
}
此时我们并没有设置文章的状态,按逻辑来说文章状态应该赋值为默认值1
可以看到是成功插入了,并且state的默认值为1
当给state赋值的时候:
@Test
void insertByCondition() {
Article article = new Article();
article.setTitle("测试文章");
article.setContent("测试文章内容");
article.setUserId(2);
article.setState(0);
Integer result = articleMapper.insertByCondition(article);
log.info("总共插入了" + result + "条数据");
}
可以看到我们成功的用一条语句完成了两条SQL语句的工作,这就是动态SQL的魅力.
可能会出现的问题
如果统一把逗号放在字段前面
当title为null时,整个SQL最前面就会多一个逗号
如果统一把逗号放在字段后面
当state为null时,整个SQL最后面会多一个逗号
为了解决这个问题,我们可以使用trim标签
3.trim标签
<trim>标签中有如下属性:
- prefix:表示整个语句块,以prefix的值作为前缀
- suffix:表示整个语句块,以suffix的值作为后缀
- prefixOverrides:表示整个语句块要去除掉的前缀
- suffixOverrides:表示整个语句块要去除掉的后缀
<insert id="insertByCondition">
insert into articleinfo
<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
<if test="title!=null">
,title
</if>
,content,uid
<if test="state!=null">
,state
</if>
</trim>
values
<trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
<if test="title!=null">
,#{title}
</if>,#{content},#{userId}
<if test="state!=null">
,#{state}
</if>
</trim>
</insert>
不设置最前面的title属性
不设置最后面的state属性
可以看到是成功插入数据了
4.where标签
当我们需要查询操作的时候,我们可以根据where条件筛选出我们需要的条件进行查询,但是有时候会选择多个条件或一个条件,因此where后面的条件也可以使用动态sql进行拼接.
这里我们先尝试使用if标签来完成功能
List<Article> selectByCondition(String uid,Integer state);
<select id="selectByCondition" resultType="com.javastudy.mybatisdemo4.model.Article">
select *
from articleinfo
where 1=1
<if test="uid!=null">and uid=#{uid}</if>
<if test="state!=null">and state=#{state}</if>
</select>
这里使用1=1这个语句可以解决and冗余的问题.
@Test
void selectByCondition() {
String uid = "2";
Integer state = 1;
List<Article> articles = articleMapper.selectByCondition(uid, state);
log.info(articles.toString());
}
可以看到这里的查询是成功的.
但是这里1=1这个条件是没有用的,这里可以使用where标签来解决这个问题.
先来说说where标签的作用,去除前导的and和生成where关键字
<select id="selectByCondition" resultType="com.javastudy.mybatisdemo4.model.Article">
select *
from articleinfo
<where>
<if test="uid!=null">and uid=#{uid}</if>
<if test="state!=null">and state=#{state}</if>
</where>
</select>
当uid和state都为null时,可以看到两个条件都被去除了
当uid为null,state不为null时
可以看到是成功的将前导0给去除了
但是where标签是不能去除后导0的
5.set标签
更新多个字段的时候也会发生更新选择的问题,和上面的where标签的问题一样,这里我们就直接来介绍set标签的作用了
set标签:生成set关键字,去除最后一个逗号
Integer updateByCondition(String uid,Integer state);
<update id="updateByCondition">
update articleinfo
<set>
<if test="uid!=null">
uid=#{uid},
</if>
<if test="state!=null">
state=#{state},
</if>
</set>
</update>
修改成功
6.foreach标签
- collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
- item:遍历时的每⼀个对象
- open:语句块开头的字符串
- close:语句块结束的字符串
- separator:每次遍历之间间隔的字符串
Integer batchDelete(List<Integer> ids);
<delete id="batchDelete">
delete from articleinfo
where id in
<foreach collection="list" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</delete>
@Test
void batchDelete() {
List<Integer> list = new ArrayList<>();
list.add(2);
list.add(6);
list.add(7);
Integer result = articleMapper.batchDelete(list);
log.info("删除了" + result + "行");
}
可以看到是删除成功了的.
注意:item的数据要和foreach标签里面的#{}的数据名称一致.
如果 Integer batchDelete(@Param("ids") List<Integer> ids);使用注解
foreach的collection要和@Param里面的内容一致.
到这里为止mybatis的全部内容就讲完了,接下来我们简单介绍一下关于注解方式
九.注解方式
1.注解演示
@Mapper
public interface UserMapper2 {
@Select("select * from userinfo")
List<User> queryAll();
@Select("select * from userinfo where id = #{id}")
User queryById(Integer id);
}
@SpringBootTest
class UserMapper2Test {
@Autowired
UserMapper2 userMapper2;
@Test
void queryAll() {
List<User> users = userMapper2.queryAll();
}
@Test
void queryById() {
User user = userMapper2.queryById(1);
}
}
通过这两个例子可以看出对于简单的sql语句,使用注解方式的写法是比xml方式是简单的,但是对于动态sql语句,注解方式就很难写的.所以这里推荐还是使用xml方式进行mybatis代码的撰写.
对于Mybatis有一个增强的框架为Mybatis Plus,很好用,建议在掌握Mybatis内容后进行了解,对之后的开发有很好的的帮助:
一篇很好的关于Mybatis Plus的文章:Mybatis-plus