文章目录
一、MyBatis 是什么
MyBatis 是一款非常优秀的 ORM 持久层框架
,去除了 JDBC 代码的繁琐操作过程,通过使用简单的 XML或注解、接口、映射原始类型操作数据库。简单来说,Mybatis 实现更加简单的程序和数据库的交互
。
ORM,即对象关系映射
,这种模式解决了面向对象与关系的数据库存在互不匹配的现象的技术。它是连接数据库的桥梁,只要提供了持久化类与表的映射关系,该框架在运行的时候就能够参照映射文件的信息,将对象持久化到数据库中。简单来说,ORM框架主要实现程序对象到关系数据库的映射,像操作对象一样操作数据库中的表
。
当接收到前端发送过来的请求,后端往往需要通过程序去访问数据库,然后获取到数据返回给前端。所以说,对于后端程序员来说,程序主要由后端代码
以及数据库
这两大部分组成,缺一不可。
在之前的时候,我们可能已经学习过 JDBC 操作数据库,但是 JDBC 操作过于繁琐
代码展示
public static void testInsert() throws SQLException {
//1、创建一个数据源对象,把数据库的位置设置到DataSource中
DataSource dataSource = new MysqlDataSource();
//1)数据库的ip 端口 数据名
((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/librarySystem?characterEncoding=utf8&useSSL=false");
//2)设置登录数据库的用户名
((MysqlDataSource)dataSource).setUser("root");
//3)设置登录数据库的密码
((MysqlDataSource)dataSource).setPassword("1111");
//2、连接数据库,进行真正的网络通信
Connection connection = dataSource .getConnection();
//3、构造SQL语句,为插入做准备
Scanner scan = new Scanner(System.in);
System.out.println("请输入学号:");
int id = scan.nextInt();
System.out.println("请输入姓名:");
String name = scan.next();
String sql = "insert into student values(?,?)";
//4、通过 Connection 及 SQL 创建操作命令对象 Statement
PreparedStatement statement = connection.prepareStatement(sql);
//5、替换占位符(指定要替换的数据库字段类型,占位索引以及需要替换进去的值)
statement.setInt(1,id);
statement.setString(2,name);
System.out.println("sql:" + statement);
//6、真正的去执行SQL语句
int ret = statement.executeUpdate();
//7、这里查询结果只是一个数据改变条数,如果是结果集 ResultSet 还需要对其进行处理
System.out.println("ret=" + ret);
//8、执行完语句之后,回收资源
statement.close();
connection.close();
}
上面的一个 JDBC 操作数据代码,所需要的步骤很多,我们需要按照模板一步步操作,需要重复写的代码很多
为了提高操作数据库的效率,MyBatis 的学习势在必行,它的存在就是为了让程序员更加方便快捷的操作数据库
二、配置 MyBatis 开发环境
2.1 创建一个数据库
既然是要操作数据库,那么就先创造一个数据库出来,具体表的实现在 3.1 节进行实现
create database if not exists library;
2.2 添加 Mybatis 框架
在创建 SpringBoot 新项目的时候,添加依赖的环节中,添加 MyBatis Framework
和 MySQL Driver
。前者是 MyBatis 框架必加无疑,后者是根据指定的数据库来添加的,毕竟数据库的有很多种,这里使用的是 MySQL ,所以该依赖必加无疑。(记得添加完后手动点击右上角的 Maven 中的 reload)
如果已经创建好了项目,想要添加这两个依赖可以先下载 EditStarts 插件
,然后如图操作
结果
2.3 配置连接字符串和 Mybatis的XML文件
配置数据库连接信息
按照从前创建的 SpringBoot 项目,创建完成之后,只要点击启动类上的绿色小三角就能够启动项目,说明项目创建成功了,但是添加了 MyBatis 框架后,直接启动就会报错
就是因为没有配置数据库的连接信息,需要我们在配置文件中添加连接信息
# 这里选择在 application.yml 文件中进行配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/library?characterEncoding=utf8&useSSL=false
username: root
password: 1111
driver-class-name: com.mysql.cj.jdbc.Driver
需要注意的是 driver-class-name
这一项,如果使用的 MySQL 是 5.X 之前的使用 "com.mysql.jdbc.Driver"
,如果是大于 5.X 使用就是 "com.mysql.cj.jdbc.Driver"
,实在搞不清楚,建议先使用前者,如果有以下报错提示就改成另一种。
配置 MyBatis 中的 XML 路径
MyBatis 的核心文件就是 接口
和XML
,以下是 MyBatis 在框架中的定位
- 前端发送来请求到后端,后端首先在
Controller 层验证参数
- 参数没有问题,就调用注入进来的
Service 层进行数据库接口的管理
,在 Service 层就会指定需要调用哪些数据接口。将 Interface(接口)注入到 Service 中,Service 就可以去调用 - 众所周知,接口不完成具体实现,普通的方法没有方法体。
XML 就是针对 Interface 的实现
,虽然代码中调用的是 Interface 的方法,实际在运行的时候 MyBatis 会根据 Interface 和Interface的实现类(XML)组合成相应的 SQL,通过调用 JDBC 将程序的业务进行实现 - 所以 MyBatis 本质上还是在调用 JDBC 的那一套使用数据库获取到数据后一层一层的返回到前端
在 MyBatis 中就需要配置 MyBatis 的 XML 路径
,之后写的查询数据库的具体操作SQL 的 XML 文件就需要放在指定的路径下,使用指定的名字类型
# 创建的 XML 文件需要放置在根路径下mapper文件夹下,名字必须为以Mapper结尾
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
三、简单操作数据库
3.1 创建表
在 library 数据库中创建两张表 author 表和 book 表,具体如下
use library;
drop table if exists author;
drop table if exists book;
create table book(
id int primary key auto_increment, -- 书籍Id
bookName varchar(100) not null, -- 书名
content varchar(1024) not null, -- 书籍内容
authorId int not null, -- 作者Id
`state` int default 1 -- 借出状态,默认为1,未借出
);
create table author(
id int primary key auto_increment, -- 作者Id
authorName varchar(100) not null, -- 作者名字
age int, -- 作者年龄
nationality varchar(250) -- 作者国籍
);
insert into book(bookName,content,authorId) values('Java学习宝典','Java宝典内容',1);
insert into author values(null,'张三',28,'中国');
3.2 insert(增加)操作
步骤一:创建实体类
//作者
@Data
public class Author {
private int id;
private String authorName;
private int age;
private String nationality;
}
步骤二:创建 Controller 层
@Controller
@ResponseBody
public class AuthorController {
@Autowired
private AuthorService service;
@RequestMapping("/setAuthor")
public void setAuthor(String authorName) {
int ret = service.setAuthor(authorName);
System.out.println("数据变更条数:"+ret);
}
}
注意:一定要添加 @Controller
和 @ResponseBody
注解
补充:这里可以采取通过 URL 中的 queryString 来传递想要插入的作者的姓名
步骤三:创建 Service 层
@Service
public class AuthorService {
@Autowired
private AuthorMapper mapper;
public int setAuthor(String authorName) {
return mapper.setAuthor(authorName);
}
}
步骤四:创建接口方法
@Mapper
public interface AuthorMapper {
public int setAuthor(String authorName);
}
注意:一定要添加 @Mapper
注解;作为接口中的方法不用写方法体,在 XML 文件中具体实现
步骤五: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.example.demo.Mapper.AuthorMapper">
<!-- 以上为 XML 固定模板内容 -->
<insert id="setAuthor">
insert into author(authorName) values(#{authorName})
</insert>
<!-- 以下为 XML 固定模板内容 -->
</mapper>
补充:
- 除去中间的 insert 标签包裹的内容,其余的为写数据库 XML 文件的固定模板,建议粘贴复制;
namespace
属性中的内容为你需要实现的接口的完整包名和接口名
- 插入数据就用 insert 标签。其中
id
和指定接口中需要实现的方法名称
是一样的,表示对接口中某个方法的具体实现,在这里就是对接口(AuthorMapper)中 setAuthor 方法的实现 - #{authorName} 中传入的就是需要进行替换的数据,这里的 authorName 要和接口中的传来的参数名保持一致,
#{}
就相当于 JDBC 中编写 SQL 语句中的?
一样 - 通过这样的方法返回的是受影响的行数
结果:通过访问 http://127.0.0.1:8080/setAuthor?authorName=李四 成功向数据库 author 表中添加一条数据
insert 操作除了返回受影响的行数外,还可以返回自增 id
,同时传参数的时候传对象
interface
public int setAuthor2(Author author);
XML
<insert id="setAuthor2" useGeneratedKeys="true" keyProperty="id" keyColumn="id">
insert into author(authorName,age,nationality) values(#{authorName},#{age},#{nationality})
</insert>
补充:
useGeneratedKeys 设置为 true
,当插入一条数据时,会使得 MyBatis 调用 JDBC 的 getGeneratedKeys 方法取出数据库内存生成的自增的主键,获取的主键id会放到指定的实体类中的主键属性。通过这个设置就可以解决在主键自增的情况下通过实体的 get 方法获取主键的问题keyProperty 用来指定数据库中返回的主键 id 给实体类中的哪个属性
,这里自然是给 Author 类的 id 属性keyColumn 就是来说明生成的键值在表中的列名
,这里自然是指 author 表中 id 列- 虽然这里参数传的是对象,但是并不影响替换参数,花括号中替换名会和传来的对象中的属性进行匹配的
单元测试代码
@SpringBootTest
class AuthorMapperTest {
@Autowired
private AuthorMapper mapper;
@Test
void setAuthor2() {
Author author = new Author();
author.setAuthorName("王五");
author.setAge(16);
author.setNationality("新加坡");
mapper.setAuthor2(author);
System.out.println("自增 id:"+author.getId());
}
}
补充:获取到的主键值就会放到 author 对象中的 id 属性中,可以通过 getId() 方法获取到
3.3 delete(删除)操作
如果每次验证写的操作数据库代码都要写对应的 Controller 和 Service 的话,是非常麻烦的。因此,在前面 insert 操作演示中,用到了单元测试
,单元测试是对软件组成的最小单元进行测试,查看测试单元的功能是否正常。
单元测试操作
首先在想要实现的接口文件中右键选择 Generate
然后选择 Test…
接着选择想要测试的方法
如果已经生成过对应的测试类了,直接 OK 就行
最后就会生成测试类,在里面写进行测试代码即可
@SpringBootTest
class AuthorMapperTest {
@Autowired
private AuthorMapper mapper;
@Test
void delAuthorById() {
}
}
注意:
@SpringBootTest
注解是我们自己需要添加的,表明这是 SpringBoot 项目的测试类;- 注入需要实现的接口类;
至此,就将继续 delete 操作示范
interface
public int delAuthorById(int id);
XML
<delete id="delAuthorById">
delete from author where id=#{id};
</delete>
单元测试代码
@Test
void delAuthorById() {
System.out.println("数据变更条数:"+mapper.delAuthorById(3));
}
结果显示
3.4 update(修改)操作
interface
public int updateAuthor(String authorName,int id);
XML
<update id="updateAuthor" >
update author set authorName=#{authorName} where id=#{id};
</update>
单元测试代码
@Test
void updateAuthor() {
System.out.println("数据变更条数:"+mapper.updateAuthor("赵六", 2));
}
结果显示
3.5 select(单表查找)操作
interface
public Author getAuthorById(int id);
XML
<select id="getAuthorById" resultType="com.example.demo.model.Author">
select * from author where id=#{id}
</select>
注意:和之前的返回数据变更条数不同,这次要返回的是 Author 类,因此需要通过 resultType 指定返回的类的包名加类名
测试单元代码
@Test
void getAuthorById() {
System.out.println(mapper.getAuthorById(1));
}
结果显示
同样如果是查询多条数据集合,用法也是类似的
interface
public List<Author> getAllAuthor();
XML
<select id="getAllAuthor" resultType="com.example.demo.model.Author">
select * from author
</select>
单元测试代码
@Test
void getAllAuthor() {
List<Author> list = mapper.getAllAuthor();
list.stream().forEach(n -> System.out.println(n));
}
结果显示
3.6 补充:参数占位符 #{} 和 ${}
在前面进行替换参数的传递时,使用的都是 #{} 的形式,还有一种 ${} 的形式
如果替换参数是数字类型, #{} 和 ${} 效果是一样的;但是如果是字符串类型,使用 ${} 就会产生错误
#{}
:预编译处理
。MyBatis 在处理 #{} 时,会将其直接替换成 ?,因此赋值的时候会自动加上一对单引号
${}
:字符直接替换
。MyBatis 在处理 ${} 时,会把有 ${} 的地方直接替换成变量的值,不会加上单引号
例如 SQL 语句:
当然在这种情况下,如果一定要使用 ${} 也是可以的,我们只需要自己手动添加引号
就行
但是这种情况下就可能带来很大的安全隐患——SQL 注入
比如在登录用户的时候需要传入用户名和密码,当采用 ${} 和手动添加单引号的方式,如果用户传入" ’ or 1='1 ",就会导致不输入正确的用户名和密码就可以成功登录的现象
结果就是不法用户随便输入一个名字,密码也不输入,就可以成功登录 id 为 2 的用户的账号。通过这样的 SQL 注入,不法分子可能删库删表,带来严重后果
当然,${} 并不是完全没有用处的,如果想要动态的修改 SQL 语句的内容,比如排序的时候,根据用户的不同选择进行升序(desc)或者降序(asc),此时就希望是直接替换,不要添加单引号
interface
public List<Author> sort(String way);
XML
<select id="sort" resultType="com.example.demo.model.Author">
select * from author order by id ${way};
</select>
注意:为了防止 SQL 注入,传入的变量名 way 一定要是程序员自己重新定义的变量名。前端传来数据后,进行校验后赋值给自己定义的变量名
单元测试代码
@Test
void sort() {
List<Author> list = mapper.sort("desc");
list.stream().forEach(n-> System.out.println(n));
}
结果展示
3.7 like(模糊)查询
模糊查询也是在查询数据库中经常使用到的,此时使用 #{} 和 ${} 都不合适
例如使用 #{}:就会发现传入的参数值张三外面有加了单引号,会报错
使用 ${} 那更不用说,有 SQL 注入风险
此时就可以使用到 mysql 的内置函数 concat() ,用于字符串的拼接
3.8 程序运行失败的可能原因
最一开始学习写 MyBatis 代码,当你自信的写好代码后,程序出错的概率是非常之大的,下面就总结一下可能导致程序出错的原因
- 是不是有缺失依赖?MyBatis 框架?MySQL 驱动?…
- 在配置文件中的数据库配置信息是否有误
- URL 的字母拼错?连接的数据库名字不对?字符集设置没?useSSL设置为 true 导致错误?
- password 是你数据库的密码吗?
- 配置的 MyBatis 的 XML 路径是否有误?classpath 可是根路径,本文设置的 mapper 文件夹是 Resource 底下和 static 文件夹同一级的
- 写的接口类上添加 @Mapper 注解了吗?@Controller、@Service 注解写了没?如果访问 URL 后返回的是数据 @ResponseBody 写了没?如果是单元测试的话,测试类上有没有写 @SpringBootTest 注解?
- 配置的 MyBatis 的 XML 路径中是不是有其他的空文件?如果有空文件就会出现 BUG
- XML 中的 namespace 写对了吗?是你想要指定实现的接口类吗?
- 写 SQL 时,id 是你要实现的具体方法吗?写的 SQL 有没有在 MySQL 控制台上运行过,确定 SQL 无误了吗?包括替换的值写的和方法传进来的对应吗?大小写之类的要注意
- 查出来了很多条数据,但是却没有用 List 接收参数之类的错误…
可能的错误还会有很多,举例的这些只是常出现的,快速定位错误,还是需要对出错时的日志
进行查看比较稳妥
未完待续~~~