MyBatis 神奇的框架:超级强大的数据库工具

发布时间:2024年01月24日

前面我们已经学习了Spring、Spring Boot、Spring MVC,现在我们要学习更加重要的MyBatis,查询数据库。

目录

MyBatis

配置MyBatis

创建数据库和表

添加MyBatis框架支持

配置字符连接串和Mybatis

配置MyBatis中的XML路径

xml文件

添加MyBatis代码

用户实体类

添加Mapper接口

添加Service

添加Controller

单元测试

单元测试构建

增、删、改操作

特殊的添加:返回自增id

单表查询

参数占位符??#{}? ${}

SQL注入

like查询

多表查询

resultType

resultMap

查询多表关联信息


MyBatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

简单来说,MyBatis就是更加简单完成程序和数据库交互的工具,也就是更简单的操作和读取数据库的工具。

对于之前学习的JDBC来说,整个操作数据库的流程非常繁琐,需要手动重复书写很多的操作步骤,这就是我们学习MyBatis的真正原因,因为它足够的方便。

配置MyBatis

创建数据库和表

首先我们先把sql语句准备好,创建好个人博客的数据库表:

-- 创建数据库
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 timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    `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 timestamp default current_timestamp,
    updatetime timestamp default current_timestamp,
    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 timestamp default current_timestamp,
		updatetime timestamp default current_timestamp,
  	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);

添加MyBatis框架支持

使用EditStarters插件快捷添加:

配置字符连接串和Mybatis

在yml文件中配置好数据库相关的参数。相比于之前JDBC繁琐的操作,如新建DBUtil等等操作,Mybatis直接在配置文件中就可以完成相关参数的设置,并且无需重复操作。

如果使用?mysql-connector-java 是 5.x 之前的,使用的是“ com.mysql.jdbc.Driver ” ,如果是?于 5.x 使用的是“ com.mysql.cj.jdbc.Driver ” 。

配置MyBatis中的XML路径

在resources目录底下新建一个xml文件,并在yml中配置XML路径:

mybatis:
  mapper-locations: classpath:mybatis/UserMapper.xml
//这个配置信息在yml文件中

xml文件

这个文件是mapper的映射文件,所有的sql语句都会在这个里面。前面配置的xml文件路径就是这个文件的路径,确保要一致

添加MyBatis代码

用户能够接触到的是控制器层,操作数据库的操作主要是在Mapper层。我们需要给代码分层

用户实体类

在UserEntity类中,定义了用户实体类,并且使用@Data注解

添加Mapper接口

数据持久层的实现,基于方法定义(Interface)和方法实现(xml)。这个Interface可以理解成为之前Servlet中的DAO类,只不过在这里用接口实现的,并且十分简洁。

xml中有所有的sql语句,interface中就是有所有的查询方法,Interface和xml文件是高度关联的,接口中的方法就是在xml文件中。

在接口中,必须要有@Mapper的注释,添加后就可以和XML文件关联起来。

接口中返回一个List,通过getAll方法拿到,那么在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.spring_mvc.mapper.UserMapper">
    <select id="getAll" resultType="com.example.spring_mvc.entity.UserEntity">
        select * from userinfo
    </select>
</mapper>

mapper 中的 namespace 和 mapper 接口需要对应起来。

select 中的id和方法名要一致,并且resultType的值要是需要返回的类的包名。

这样一个查询所有的用户的方法getAll就完成了。

添加Service

通过@Service把UserService交给IoC容器后,用@Autowired注解把UserMapper接口拿到就可以使用getAll方法。返回的是UserEntity这个类组成的列表。

添加Controller

最后在Controller中配置好了路由映射后,就可以开始在浏览器中访问了。

这样就可以访问到数据库的数据了。相比于Servlet中的JDBC查询,简化了很多内容,这就是框架帮我们简化的内容,框架的好处就在这里体现出来了。

并且采用分层思想,哪一部分出问题了去查看哪一部分的代码就可以了。

单元测试

单元测试就是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。

在之前的getAll方法中,我们最终是在浏览器中看到的结果,也就是说从实体类到Mapper接口,到Service,到Controller和配置文件中都没有错误,这是我们想要的结果。但是如果调用链很长我们需要测试的仅仅只有一个方法,这个时候就可以使用单元测试来完成。

单元测试构建

在Mapper接口中,通过Generate可以生成测试方法

此时的@Param目的是给需要传入进来的id重命名,在这里不重命名也可以,但是如果有多个参数就一定要命名。

例如我们现在有一个getUserById方法想要测试,在创建测试中勾选这个方法,就可以在Test目录中找到相关的测试方法了。

此时我们新建一个根据Id查询User的方法,只需要在mapper和xml文件中完成相关的代码就可以执行单元测试,无需把所有的调用链都完成。

增、删、改操作

在xml中,增加使用insert标签,需要指定id,但是不需要指定resultType,因为并不需要返回值,所以不需要指定返回类型。

一个user对象,除了自增主键的id和创建时间,需要用户自己添加的就是用户名和密码了。我们在传入的时候,是直接传入的一个对象,而不是仅仅把对象的两个属性传入进来。这样做可以在之后修改的时候更加简单。

删除操作是根据用户的Id来删除。

使用delete标签来删除,传入的是Id。

使用了@Transactional这个注解,这个注解表示这个测试方法测试过后需要回滚,把数据回滚至之前的状态。

这里实现的方法是根据Id来更改用户名,所以传参数的时候需要传入两个值,一个是Id,一个是name。

特殊的添加:返回自增id

在刚刚演示的增删改操作中,方法返回的都是int类型,返回的都是受影响的行数。如果想要返回的是自增的id,也可以做到:

和之前的add方法,基本上一样,不过多了几个参数:

  • useGeneratedKeys:是否拿到表的自增Id(默认值是false)
  • keyProperty:把刚刚拿来的自增Id放到哪一个属性中(默认是未设置unset)

也就是说,通过这两个参数的设置,拿到自增的ID并且赋值到user的Id中,此时可以通过get方法拿到Id:

单表查询

参数占位符??#{}? ${}

在前面的xml文件中,我们使用了两种传参数的占位符,这两种方法都可以。

区别在于:

  • #{}:预编译处理:在处理时,会将SQL中的 #{} 替换成?,然后再使用PrepareStatement的set方法来赋值。
  • ${}:字符直接替换:在处理时,把 ${} 的值直接替换成变量的值。

先说结论:两种方式缺一不可。但是尽量使用#{},因为${}会出现SQL注入这样的问题。

那为什么不完全用#{}呢?因为在某些情况只能是${}才能完成。

例如在按照某种顺序查询的时候,如果使用#{}的方式,会出现无法查询的情况:

 select * from userinfo order by id ${sort}
 select * from userinfo order by id #{sort}

例如使用${sort}时,可以正常的完成查询。而使用#{sort}时,就因为传递的值为String拼接而成,导致拼接的时候会加上单引号,导致sql错误。

SQL注入

当我们需要查询数据库,此时被人恶意拼接某些字符串,就会导致数据泄露。

select * from userinfo where username='${name}' and password='${password}'

正常输入的用户名为zhangsan,密码为123,此时数据库的查询语句就会变成:

select * from userinfo where username='zhangsan' and password='123'

但是如果我们恶意拼接字符串? ' or 1='1

这样,这个sql语句就变成了:

这样不管前面的用户名和密码输入的是什么,最后的 1 = '1' 总是成立的,这样岂不是就算没有正确的密码也能返回用户信息了?

所以,使用 ${} 是非常危险的行为!需要仔细仔细再仔细!

like查询

like查询是模糊查询:

select * from userinfo where username like '%zhang%' ;

正常情况下,使用like查询可以通过单引号内加%的形式来匹配,前后都有百分号则表示只要出现了zhang这个关键字就能够查询到。

但是由于在mybatis中需要使用参数占位符,并且使用${}会出现sql注入的现象,所以只能使用#{}来代表参数,这个时候代码就会变成这样:

select * from userinfo where username like '%#{username}%' ;

此时的username为zhang。但是因为zhang为字符串,最终在mysql中执行的语句就变成了:

select * from userinfo where username like '%'zhang'%' ;

此时就会出现zhang这个字符串多出来了''一串单引号,拼接到sql语句中就报错了。

所以为了确保安全,又要保证模糊查询生效,解决办法就是使用mysql内置的concat函数来完成,

concat可以拼接多个字符串,如:

select * from userinfo where username like concat('%',#{username},'%');

这样就可以正常的使用模糊查询了。

多表查询

增、删、改操作,在xml中是无需返回类型的。

而查询操作需要返回一个resultType类型,这个类型对应着实体类。

也就是说,实体类中的字段名要和数据库中的名字匹配才能够查询到具体的值。

此时使用resultType可以正常查询到相关的值。

resultType

resultType的好处是方便,只需要在查询操作时返回一个对应的类就可以了。但是一旦实体类是的成员名和数据库中的不匹配,那么就会查询不到。

resultMap

  • 字段名称和数据库中的名称不一样时可以使用resultMap
  • ?对?和?对多关系可以使用?resultMap 映射并查询数据

resultMap的基础格式:

其中,第一行的id指的是这个map的id,也就是和后面匹配的时候的id:

id column指的是主键映射,result指的是普通的字段和属性映射。

此时就算数据库中的字段和实体类中的字段名字不一样,在映射过后也可以查询到。

查询多表关联信息

我们需要查询文章表的信息,文章表中有这样一些信息,我们创建好实体类:

我们还需要用户表中的用户名,也就是需要同时查询两张表的信息。

我们在新建一个实体类ArticleInfoVO,在这个实体类中,加入了需要查询的用户名,并且继承ArticleInfo类。这样只需要在接口和xml中定义新的这个实体类就能够拿到两张表的信息。

此时我们将ArticleMapper和xml对应上:

并且把相关的映射对象修改好,SQL语句:

        select a.*,username from articleinfo a

        left join userinfo u on u.id=a.uid

        where a.id=#{id}

把articleinfo重命名成a,userinfo重命名成u,join on的条件为 id = uid ,总语句传入的参数为articleinfo表的id。

此时就可以用测试方法来测试了。

但还会发现一个问题,最终结果并不会打印username的方法,这是因为Lombok在重写toString的时候,只会重写打印当前的方法,我们还需要把toString再生成一遍,并且选中重写当前方法和父类的方法。

这样就能够查询多个表,并且在测试方法中打印出来。

文章来源:https://blog.csdn.net/m0_62319039/article/details/135754171
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。