Mybatis入门

Mybatis是持久层(也就是三层架构中的Dao层)的一款框架,简化了JDBC的开发。内容来自视频->Day08-14. Mybatis-入门-快速入门程序_哔哩哔哩_bilibili

入门

快速入门

开启一个模版程序,实现使用Mybatis查询所有用户数据

  1. 准备工作(创建springboot文件,定义实际类User,数据库表user)
  2. 添加mybatis相关依赖,配置mybatis(数据库连接信息)
  3. 编写SQL语句(注解/XML)

创建springboot文件,与springboot入门中的创建方法基本一致,注意要勾选Mybatis Framework和MySQL Driver依赖。

实际类要与user的字段保证一致,int->Integer,varchar->String,tinyint->Short,主要使用其包装类,同时声明相应的getset方法、构造器、toString方法。

mybatis配置:

1
2
3
4
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
spring.datasource.username=root
spring.datasource.password=1234

编写SQL语句(注解/XML)

1
2
3
4
5
6
@Mapper//在运行时,MyBatis会扫描这个接口,并为其生成一个实现类对象(代理对象),并交给IOC管理
public interface UserMapper {
//查询
@Select("select * from user")
public List<User> list();
}

单元测试

1
2
3
4
5
6
7
8
9
10
11
12
@SpringBootTest//springboot整合单元测试的注解
class SpringbootMybatisQuickstartApplicationTests {
@Autowired
private UserMapper userMapper;
@Test
public void testListUser() {
List<User> userList = userMapper.list();
for (User user : userList) {
System.out.println(user);
}
}
}

配置SQL提示

在上边的SQL语句中,如果没有配置,那么就不会显示相应的提示,即使写错了也不会提醒,这对于开发是非常不利的。好在Idea中可以为其配置MySQL数据库连接。

  • 选中SQL语句,右键,配置为MySQL语句
  • 点击右侧边栏的数据库选项,点击加号,数据源选择MySQL,填写host、port、用户和密码、数据库

数据库连接池

  • 数据库连接池是个容器,负责分配、管理数据库连接
  • 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
  • 能够释放空闲时间超过最大空闲时间的连接,避免因为没有释放连接引起的数据库连接遗漏
  • 好处:资源重用 提升系统响应速度 避免数据库连接遗漏

标准接口:DataSource

  • 官方提供的数据库连接池接口,由第三方组织实现此接口
  • 功能:获取连接

常见产品:C3P0、DBCP、Druid、Hikari

lombok

在序-快速入门中,在实际类的编写中我们自己手动构造了getset、toString等方法,较为繁琐。改进方法就是使用lombok。lombok是一个实用Java类库,能通过注解的形式自动生成getset方法、构造器、toString等方法,并可以自动化生成日志日志变量,简化Java开发、提高效率。

注解 作用
@Getter、@Setter 提供getset方法
@ToString 提供tostring方法
@EqualsAndHashCode 重写equals和hashCode方法
@Data 上边三个的整合
@NoArgsConstructor 无参构造器
@AllArgsConstructor 含餐构造器

直接添加注解是会报错的,还需要导入lombok的依赖,可以写注解然后根据IDEA自动导入依赖。

CRUD操作

CRUD操作是mybatis使用的核心,这一部分以实操为主

首先进行工程的准备工作,和入门的工程基本一致。

关于实体类的创建的注意点:

  • 注意在数据库表结构中字段名是使用下划线分隔的,但是在实体类中我们采用驼峰命名

在mybatis中实现删除操作

根据主键删除

在Mapper接口中编写如下方法和语句,#{…}是占位符,用于将方法的参数提取出来。

1
2
3
//删除
@Delete("delete from user where id =#{id}")
public void delete(Integer id);

日志输出

1
2
#配置mybatis的日志,指定输出到控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

日志的输出如下

==> Preparing: delete from user where id =? ==> Parameters: 9(Integer) <== Updates: 1

可以看到输出的SQL语句带有“?”,这个就被称为预编译SQL。

它有两大优势:

  • 性能更高
  • 更安全(防止SQL注入)

为什么性能更高?

需要从SQL语句具体的执行过程来分析

SQL语句执行

graph LR
    A[Java] ==> B(SQL语法解析检查)
    
    subgraph SQL处理流程[缓存]
        B ==> C(优化SQL)
        C ==> D(编译SQL)
    end
    
    D ==> E(执行SQL)

对比两种方式

第一种

delete from user where id =1

delete from user where id =2

delete from user where id =3

第二种

delete from user where id = ?

1

2

3

这两种性能的差别就是因为这个缓存的过程,在Java发送SQL语句之后,会先经过检查、优化、编译,然后被缓存起来,之后再发送的时候会先检查缓存种是否有这个SQL语句,如果有就直接执行了。

第一种的话由于三条SQL语句每条都不相同,所以需要编译三次,而第二种的话由于是预编译的形式,所以只用编译一次,后边三条SQL语句就可以直接执行。

由此我产生一个问题,使用多态性是否也同样能提高Java的效率?

为什么更安全?

这里主要是与另一种占位符作对比

#{…}

  • 执行SQL时,会将#{…}替换为?,生成预编译SQL,会自动设置参数值

  • 使用时机:参数传递,都使用#{…}

${…}

  • 拼接SQL。直接将参数拼接在SQL语句中,存在SQL注入问题
  • 使用时机:很少使用,如果对表名、列表进行动态设置时使用

SQL注入:通过操作输入的数据来修改实现定义好的SQL语句,以达到执行代码对服务器进行攻击的方法

举个栗子🌰

账户的登录操作其实就是查询数据库中用户表的相应username和password是否都存在

1
select count(*) from emp where username='miku' and password ='123456';

如果count(*)>=1,那么我们就认为密码正确,就可以放行了。

若使用的是${…}

那么就是直接拼接的形式,如果传入的是这样的(“用户名任意填”,”‘ ’ or ‘1’=‘1”)

SQL语句就会变成

1
select count(*) from emp where username='miku' and password ='' or '1'='1';

这样就是一个恒成立的条件,那么也会直接放行。

这里只是举个例子,实际上现在是个网站都会防SQL注入,渗透也没有这么简单。

若使用的是#{…}

那么就会直接传递给?,就会直接拿传递的参数去数据库查询,不会更改SQL的基本逻辑。

在mybatis中实现新增操作

基本插入

最简单的插入,不含占位符,无参数传递, 也最没有用

1
2
3
@Insert("insert into user(user.username, user.name, user.gender, user.age) " +
"values('test','测试插入SQL',1, 18)")
public void insert();

使用占位符传递,注意与实用类的属性名保持一致,使用小驼峰命名

1
2
3
@Insert("insert into user(id,user.username, user.name, user.gender, user.age) " +
"values(#{id},#{username},#{name},#{gender},#{age})")
public void insert(User user);

获取主键

在@Insert注解上添加注解@Options(keyProperty = “id”,useGeneratedKeys = true)会自动将生成的主键值,赋值给emp对象的id属性

在mybatis中实现更新操作

很简单,会SQL语句就会更新操作

直接上代码

1
2
3
@Update("update user set username=#{username}"+
", name=#{name},gender=#{gender}, age=#{age} where id=#{id}")
public void update(User user);

在mybatis中实现查询操作

根据Id查询

1
2
@Select("select * from user where id = #{id}")
public User getById(Integer id);

代码比较简单,就是设置一个传参的SQL语句就可以了

但是有一个问题,比如日期update_time这些MySQL和Java命名规范不一样的字段,是不会进行自动封装的,也就是说直接查是查不到的。相应有三种解决方案:

  • 配置自动映射
1
mybatis.configuration.map-underscore-to-camel-case=true

这是最简单也是最推荐的一种方案,只要保证符合命名规范,就能够自动封装

  • 使用别名
1
@Select("select update_time updateTime from user where id = #{id}")
  • 使用@Results注解
1
2
3
4
@Results({
@Result(property = "updateTime", column = "update_time"),
@Result(property = "entryDate", column = "entry_date")})
@Select("select * from user where id = #{id}")

后两种都比较繁琐,很少用

条件查询

考虑下面一个需求:

查询员工,根据员工的姓名、性别、入职时间搜索满足条件的员工信息。要求:员工姓名支持模糊匹配,性别精确匹配,入职时间进行范围查询。对查询的结果根据最后修改时间进行倒序排序。

编写出的SQL语句应该是这样的:

1
2
select * from user where name like '%张%' and gender=1 and entry_date between 
'2010-01-01' and '2020-01-01' order by update_time desc

用Mybatis实现:

1
2
@Select("select * from user where name like concat ('%',#{name},'%') and gender=#{gender} and entry_date between #{begin} and #{end} order by update_time desc")
public List<Emp> list(String name ,Short gender,LocalDate begin,LocalDate end);

注意,在这里使用了concat (‘%’,#{name},‘%’)函数,这是因为如果直接替换为‘%#{name}%’,是不合法的,因为?占位符不能出现在引号内,只能使用${…},可是这样就会有性能低、安全性低的问题。那么就使用了SQL的函数concat,它用于将多个字符串拼接起来,这样就解决了问题。

参数名说明

在SpringBoot1.X版本,参数名前必须要加上@Param(“字段名”),才能进行传递,这是因为在这个版本是不会把形参名保留下来,所以就必须要加上注解。

XML映射文件

定义规范

  • XML的文件名称与Mapper接口的名称一致,并且放置在相同包下(同包同名)
  • XML的namespace属性与Mapper接口的全限定名一致
  • XML中SQL语句的id与Mapper接口中的方法名一致,有返回值的要写resultType属性,这个是单条记录所封装的类型(也就是说如List结构,要写他的单挑记录的数据类型)

动态SQL

学习动态SQL就是要学习其响应的标签

1.

  • 用判断条件是否成立,如果条件为true,则拼接SQL
  • 形式:
1
<if test="name != null">...</if>

2.

  • where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句开头的and或or

3.

  • 动态地在行首插入set关键字,并会删除额外的逗号。(用在update语句中)

4.

  • SQL语句
1
delete from user where id in(12,13,14);
  • 接口方法
1
2
//批量删除
public void deleteBytes(List<Integer> ids);
  • XML映射文件
1
2
3
4
5
6
<delete id="deleteBytes">
delete from user where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>

标签属性说明:

collection:集合名称

item:集合遍历出来的元素/项,与下边占位符名称一致

separator:每一次遍历使用的分隔符

open:遍历开始前拼接的片段

close:遍历结束后拼接的片段

5.

为提高复用性,可以将sql用标签记录,并使用标签调用

1
2
3
4
5
6
<!--纪录-->
<sql id="commonSelect">
select id,username,name,gender,age from user
</sql>
<!--调用-->
<include refid="commonSelect"/>