Mybatis操作数据库(入门)
一、什么是Mybatis
Mybatis是一款优秀的持久层框架,用于简化JDBC的开发。
其中的持久层指的就是持久化操作的层,通常指数据库访问层(dao),是用来操作数据库的。
简单来说Mybatis是更简单完成程序和数据库交合的框架,也就是更简单的操作和读取数据库的工具。
二、Mybatis入门
(一)准备工作
1、创建工程
在创建SpringBoot工程中,勾选SQL中的MyBatis Framework和MySQL Driver选项。
工程创建完成之后,会自动在pom.xml文件中添加Mybatis的依赖包和驱动包。
<!--Mybatis 依赖包-->
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version>
</dependency>
<!--mysql驱动包-->
<dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope>
</dependency>
2、数据准备
创建用户表,并创建对应的实体类User。
-- 使用数据库
USE mybatis_test;-- 创建表[用户表]
DROP TABLE IF EXISTS user_info;
CREATE TABLE `user_info` (`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,`username` VARCHAR ( 127 ) NOT NULL,`password` VARCHAR ( 127 ) NOT NULL,`age` TINYINT ( 4 ) NOT NULL,`gender` TINYINT ( 4 ) DEFAULT '0' COMMENT '1-男 2-女 0-默认',`phone` VARCHAR ( 15 ) DEFAULT NULL,`delete_flag` TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常,1-删除',`create_time` DATETIME DEFAULT now(),`update_time` DATETIME DEFAULT now() ON UPDATE now(),PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;-- 添加用户信息
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.user_info( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
创建对应的实体类UserInfo:
package com.bite.mybatis.model;import lombok.Data;@Data
public class UserInfo {private Integer id;private String username;private String password;private Integer age;private Integer gender;private String phone;private Integer deleteFlag;}
(二)配置数据库连接
Mybatis中要连接数据库,需要数据库相关参数配置。
在application.yml文件中,配置内容如下:
#数据库配置datasource:url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=trueusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
(三)写持久层代码
在项目中,先创建出持久层接口UserInfoMapper:
package com.bite.mybatis.mapper;import com.bite.mybatis.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;//定义一个查询接口,交给Spring来管理
@Mapper
public interface UserInfoMapper {//表明这个方法对应的是这条sql语句@Select("select * from user_info")List<UserInfo> selectAll();
}
- Mybatis的持久层接口规范一般都叫xxxMapper。
- @Mapper注解:表示的是Mybatis中的Mapper接口,程序运行时,框架会自动生成接口的实现类对象,并交给Spring的IOC容器管理。
- @Select注解:代表的就是select查询,表示下面这个方法的作用对应到数据库就是这条sql语句。
(四)测试
关于生成测试代码,我们在需要测试的类中点击鼠标右键,选择Generrate中的Test,再勾选要测试的方法即可生成测试代码。
package com.bite.mybatis.mapper;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoMapperTest {@Autowiredprivate UserInfoMapper userInfoMapper;@Testvoid selectAll() {System.out.println(userInfoMapper.selectAll());}
}
@Autowired注解:将Spring中管理的UserInfoMapper对象注入到当前对象中。
测试结果:

显示了表中所有数据。
三、Mybatis的基本操作
(一)打印日志
我们只需要在配置文件中添加如下:
如果是在yml文件中:
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
如果是在properties文件中:
#指定mybatis输出日志的位置,输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
配置了文件之后,对数据库进行操作会有类似于下面语句:

(二)数据传递
当我们进行查询时,如查找id=4的用户,对应的sql语句就是:select*from user_info where id=4
@Select("select * from user_info")List<UserInfo> selectAll();
但是这样代码就被写死了,解决方法是在selectAll方法中添加一个参数,讲方法中的参数传递给sql语句。
在Mybatis中,使用#{}的方式获取方法中的参数到sql语句中。
@Select("select * from user_info where id = #{id}")UserInfo selectAllById(Integer id);
进行测试:
@Testvoid selectAllById() {System.out.println(userInfoMapper.selectAllById(3));}

⭐️另外,由于数据库相关参数命名的规范与java不同,也可以通过@param注解,设置参数的别名:
@Select("select * from user_info where id = #{id}")UserInfo selectAllById(@Param("id") Integer number);
(三)增(insert)
在UserInfoMapper接口中:
@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
测试代码:
@Testvoid insert() {UserInfo userInfo=new UserInfo();userInfo.setUsername("zhaoLiu");userInfo.setPassword("zhaoLiu");userInfo.setGender(2);userInfo.setAge(21);userInfo.setPhone("18612340005");userInfoMapper.insert(userInfo);}

返回主键
insert语句默认返回的是受影响的行数。
但是在有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插入数据的id。
如果想要拿到自增id,就需要在Mapper接口的方法上添加一个Options的注解。
useGeneratedKeys:使 MyBatis 使用 JDBC 的getGeneratedKeys方法获取数据库内部生成的主键(如 MySQL、SQL Server 的自动递增字段),默认值false。keyProperty:指定唯一识别对象的属性,MyBatis 会用getGeneratedKeys的返回值或insert语句的selectKey子元素设置其值,默认未设置。
(四)删(delete)
在UserInfomapper接口中:
@Delete("delete from user_info where id = #{id}")
void delete(Integer id);
测试代码:
@Testvoid delete() {userInfoMapper.delete(1);}

(五)改(update)
在UserInfoMapper接口中:
@Update("update user_info set username=#{username} where id=#{id}")
void update(UserInfo userInfo);
(六)处理数据库与java命名规范不一致的问题
1、起别名
在sql语句中,给列名起别名,保持别名和实体类属性名一样。
@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, " +"create_time as createTime, update_time as updateTime from user_info")
public List<UserInfo> queryAllUser();
如果sql语句太长,可以通过“+”相连接。
2、结果映射
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")
@Results({@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser();
如果有其他sql也需要复用这个映射关系,可以给这个Results定义一个名称:
@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info")//在此处定义了id,value是映射关系
@Results(id = "resultMap", value = {@Result(column = "delete_flag", property = "deleteFlag"),@Result(column = "create_time", property = "createTime"),@Result(column = "update_time", property = "updateTime")
})
List<UserInfo> queryAllUser();@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time " +"from user_info where id = #{userid} ")//此处复用了上面的Results,value为上面的id
@ResultMap(value = "resultMap")
UserInfo queryById(@Param("userid") Integer id);
3、开启驼峰命名(推荐)
通常数据库列使用蛇形命名法进行命名 (下划线分割各个单词),而 Java 属性一般遵循驼峰命名法约定。
为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。
mybatis:configuration:map-underscore-to-camel-case: true #配置驼峰自动转换
在yml文件中做上述配置,java代码不需要做任何处理。
四、Mybatis XML配置文件
Mybatis的开发有两种方式:
- 注解
- XML
上面学习的是注解的方式,主要是用来完成一些简单的增删查改的功能,如果要实现复杂的sql功能,建议使用XML来配置映射语句,也就是将sql语句写在XML配置文件中。
Mybatis XML的方式需要以下两步:
- 配置数据库连接字符串和Mybatis
- 写持久层代码
(一)配置数据库连接字符串和Mybatis
如果是在application.yml文件中:
mybatis:# 配置 mybatis xml 的文件路径,在 resources/mapper 创建所有表的 xml 文件mapper-locations: classpath:mapper/**Mapper.xml
(二)写持久层代码
持久层代码分两部分:
- 方法定义interface
- 方法实现xml
1、添加Mapper接口
package com.bite.mybatis.mapper;import com.bite.mybatis.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;
@Mapper
public interface UserInfoMapperXML {}
2、添加UserInfoMapperXML.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.bite.mybatis.mapper.UserInfoMapperXML"></mapper>
在application.yml文件中与UserInfoMapperXML.xml文件的路径对应。

我们可以下载一个MybatisX的插件,这样可以在UserInfoMapperXML.java文件和UserInfoMapperXML.xml文件对应处相互跳转。
(三)测试代码
Mapper接口代码:
package com.bite.mybatis.mapper;import com.bite.mybatis.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.List;
@Mapper
public interface UserInfoMapperXML {List<UserInfo> selectAll();}
XML文件代码:
⭐️当我们在mapper接口下写出方法,可以自动在xml文件中生成带select标签的语句,在其中可以写对应的SQL语句。
<?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.bite.mybatis.mapper.UserInfoMapperXML"><!-- 查询数据--><select id="selectAll" resultType="com.bite.mybatis.model.UserInfo">select * from user_info</select></mapper>
此时mapper接口中的selectAll方法就与xml文件中select标签中的语句相绑定。
测试代码:
package com.bite.mybatis.mapper;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
class UserInfoMapperXMLTest {@Autowiredprivate UserInfoMapperXML userInfoMapperXML;@Testvoid selectAll() {System.out.println(userInfoMapperXML.selectAll());}
}
针对使用XML文件对Mybatis的操作,增删查改的操作与演示一致。
(四)其他查询操作
1、#{ }与${ }
Mybatis参数赋值有两种方式,前面已经提到过#{ }进行赋值,接下来来看二者的区别。
(1)先看Integer类型的参数
mapper接口:
//探究#{ }和${ }的区别@Select("select username, `password`, age, gender, phone from user_info where id= #{id}")UserInfo queryById(Integer id);@Select("select username, `password`, age, gender, phone from user_info where id= ${id}")UserInfo queryById2(Integer id);
测试代码:
@Testvoid queryById() {System.out.println(userInfoMapper.queryById(1));}@Testvoid queryById2() {System.out.println(userInfoMapper.queryById2(1));}
queryById方法结果:

queryById2方法结果:

从以上结果日志中可以看出,输出的sql语句:
//使用#{}
select username, `password`, age, gender, phone from user_info where id= ?//使用${}
select username, `password`, age, gender, phone from user_info where id= 1
在使用#{}时,输入的参数并没有在后面进行拼接,id的值使用?进行占位,这种SQL,被称之为“预编译SQL”。
而使用${},则被称为“即时编译”。
(2)String类型的参数
mapper接口:
@Select("select username, `password`, age, gender, phone from user_info where username= #{name} ")UserInfo queryByName(String name);@Select("select username, `password`, age, gender, phone from user_info where username= ${name} ")UserInfo queryByName2(String name);
测试代码:
@Testvoid queryByName() {System.out.println(userInfoMapper.queryByName("admin"));}@Testvoid queryByName2() {System.out.println(userInfoMapper.queryByName2("admin"));}
queryByName方法结果:

queryByName2方法结果:

如果将queryByName2方法改为:
//在${}两端加上引号:
@Select("select username, `password`, age, gender, phone from user_info where username= '${name}' ")UserInfo queryByName2(String name);

从上面两个例子可以看出:
#{}使用的是预编译 SQL,通过?占位的方式,提前对 SQL 进行编译,然后把参数填充到 SQL 语句中。#{}会根据参数类型,自动拼接引号''。${}会直接进行字符替换,一起对 SQL 进行编译。如果参数为字符串,需要加上引号''。
⭐️(3)#{}相对于${}的优点
- 能提高效率:绝大多数情况下,某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的语法解析、SQL 优化、SQL 编译等,则效率就明显不行了。预编译 SQL,编译一次之后会将编译后的 SQL 语句缓存起来,后面再次执行这条语句时,不会再次编译(只是输入的参数不同),省去了解析优化等过程,以此来提高效率。
- 防止SQL 注入:SQL注入是通过操作输入的数据来修改事先定义好的 SQL 语句,以达到执行代码对服务器进行攻击的方法。由于没有对用户输入进行充分检查,而 SQL 又是拼接而成,在用户输入参数时,在参数中添加一些 SQL 关键字,达到改变 SQL 运行结果的目的,也可以完成恶意攻击。
2、排序功能
从上面的例子中,可以得出结论:
${} 会有 SQL 注入的风险,所以我们尽量使用#{}完成查询。既然如此,是不是${}就没有存在的必要性了呢?当然不是。接下来我们看下${}的使用场景。
当我们想使用排序功能时:
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +"from user_info order by id ${sort} ")
List<UserInfo> queryAllUserBySort(String sort);
使用 ${sort} 可以实现排序查询,而使用 #{sort} 就不能实现排序查询了。
注意:此处 sort 参数为 String 类型,但是 SQL 语句中,排序规则是不需要加引号
''的,所以此时的${sort}也不加引号。
⭐️如果使用#{}:
可以发现,当使用 #{sort} 查询时,asc 前后自动给加了引号,导致 sql 错误。
#{}会根据参数类型判断是否拼接引号''如果参数类型为 String,就会加上 引号。
3、like查询
like 使用 #{} 报错:
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +"from user_info where username like '%#{key}%' ")
List<UserInfo> queryAllUserByLike(String key);
把 #{} 改成 可以正确查出来,但是{} 存在 SQL 注入的问题,所以不能直接使用 ${}。
解决办法:使用 mysql 的内置函数 concat () (将字符串拼接)来处理,实现代码如下:
@Select("select id, username, age, gender, phone, delete_flag, create_time, update_time " +"from user_info where username like concat('%',#{key},'%')")
List<UserInfo> queryAllUserByLike(String key);
