文章目录
什么是 MyBatis?
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
持久层
持久层:指的就是持久化操作的层,通常指数据访问层(dao),是用来操作数据库的
简而言之:MyBatis是更简单完成程序和数据库交互的框架,也就是更简单的操作和读取数据库工具
前期准备工作
依赖配置
1.创建springboot工程
2.要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。
如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
SpringBoot 3.X对应MyBatis版本为3.X
对应版本的信息可参考官方网站:https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
3. 导入mysql驱动的依赖
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
4.配置数据库连接字符串
Mybatis中要连接数据库,需要数据库相关参数配置
application.yml:
spring:
application:
name: demo
datasource:
url: jdbc:mysql://127.0.0.1:3306/java113?characterEncoding=utf8&useSSL=false// 其中java113是我的数据库名
username: root //登录名
password: 123456//密码
driver-class-name: com.mysql.cj.jdbc.Driver
application.properties:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/java113?
characterEncoding=utf8&useSSL=false
spring.datasource.username=root
spring.datasource.password=123456
5.打印日志
在配置文件中进行以下配置
application.yml:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
application.properties:
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
6.配置驼峰自动转换
数据准备
创建用户表,并创建对应的实体类User
DROP DATABASE IF EXISTS java113;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
USE java113;
-- 创建表
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
@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;
private Date createTime;
private Date updateTime;
}
单元测试
测试类上添加了注解@SpringBootTest,该测试类在运行时,就会自动加载Spring的运行环境.
我们通过@Autowired这个注解,注入我们要测试的类,就可以开始进行测试了
使⽤Idea自动生成测试类
勾选需要生成的测试类,一路ok就可生成对应的测试类
Mybatis的开发有两种方式:
- 注解
- XML
Mybatis的基础操作(注解)
@Select 实现查询功能
参数传递
查找id为4的用户信息
sql语句:
select * from user_info where id=4
@Select("select id,username,password,age,gender,phone,delete_flag,update_time from user_info where id=4")
UserInfo selectById();
SQL语句中的id值不能写成固定数值,需要变为动态的数值
解决方案:使用#{} 的方式获取方法中的参数
@Select("select* from user_info where id=#{id}")
UserInfo selectAllById(Integer id);
测试用例:
@Test
void selectById() {
System.out.println(userInfoMapper.selectAllById(1));
}
默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样) 这种SQL我们称之为"预编译SQL"
为了使数据库的查询结果和返回值类型中的属性能够自动匹配,通常会对 MySQL 数据库和 JavaBean 采用同一套命名规则,即 Java 命名驼峰规则,这样就不需要再做映射了(数据库表字段名和属性名不一致时需要手动映射)。
查询结果中字段不一致的为null,没有赋值成功
和数据库中的字段不一致,需要映射
方法有:
1.起别名
2.结果映射
3.驼峰转换
起别名
和mysql一样通过as关键字
@Select("select id, username, `password`, age, gender, phone, delete_flag as deleteFlag, "
+"create_time as createTime, update_time as updateTime from user_info")
List<UserInfo> queryAllUser();
结果映射
@Select("select id, username, `password`, age, gender, phone, delete_flag,"
+"create_time , update_time from user_info")
@Results(id = "resultMap",value = {
@Result(column = "delete_flag", property = "deleteFlag"),
@Result(column = "update_time", property = "updateTime"),
@Result(column = "create_time", property = "createTime"),
}
)
List<UserInfo> queryAllUser2();
@Results 各个属性的含义:
id:表示当前结果集声明的唯一标识;
value:表示结果集映射关系;
@Result:代表一个字段的映射关系。其中,column 指定数据库字段的名称,property 指定实体类属性的名称,jdbcType 数据库字段类型,id 为 true 表示主键,默认 false。
如果其他SQL,也希望可以复用这个映射关系,可以给这个Results定义⼀个名称
@ResultMap 来引用映射结果集
@Select("select* from user_info where id=#{id}")
@ResultMap(value = "resultMap")
UserInfo selectAllById(Integer id);
这样不需要每次声明结果集映射时都复制冗余代码,简化开发,提高了代码的复用性。
驼峰转换
在配置文件中添加配置
mybatis:
configuration:
map-underscore-to-camel-case: true #配置驼峰自动转换
驼峰命名规则:abc_xyz=>abcXyz
• 表中字段名:abc_xyz
• 类中属性名:abcXyz
@Param 映射多个参数
设置参数的别名,如果使用 @Param 设置别名,#{}里面的属性名必须和@Param 设置的⼀样
@Select("select username, `password`, age, gender, phone from user_info where
id= #{userid} ")
UserInfo queryById(@Param("userid") Integer id);
@Insert 实现插入功能
插入一条用户信息
使用@Insert注解
@Insert({
"insert into user_info(id,username,`password`,age,gender) VALUES(#{id},#{username},#{password},#{age},#{gender})"
})
Integer InsertUser(UserInfo userInfo);
测试用例:
@Test
void insertUser() {
UserInfo userInfo=new UserInfo();
userInfo.setId(10);
userInfo.setAge(16);
userInfo.setUsername("cyn");
userInfo.setGender(0);
userInfo.setPassword("132456");
Integer result=userInfoMapper.InsertUser(userInfo);
System.out.println("影响行数:"+result+" id:"+userInfo.getId() );
}
如果设置了 @Param 属性,#{}需要使参数.属性来获取
@Insert({
"insert into " +
"user_info(id,username,`password`,age,gender)"
+"VALUES(#{userInfo.id},#{userInfo.username},#{userInfo.password},#{userInfo.age},#{userInfo.gender})"
}) Integer insertUser2(@Param("userInfo")UserInfo userInfo);
Insert语句默认返回的是受影响的行数
但有些情况下,数据插入之后,还需要有后续的关联操作,需要获取到新插⼊数据的id
比如订单系统
当我们下完订单之后,需要通知物流系统,库存系统,结算系统等,这时候就需要拿到订单ID
@Options 获取自增id的值
@Options (useGeneratedKeys = true,keyProperty = "id",keyColumn = "id")
useGeneratedKeys = true
-true 自动递增 默认为false
- keyProperty = "id"
-id 这个id为实体类中的id
- keyColumn = "id"
-id 这个id为数据库中的字段id
设置useGeneratedKeys=true 之后,方法返回值依然是受影响的行数 自增id 会设置在上述 keyProperty 指定的属性中.
@Update 实现更新功能
更新用户id为5的名字为张三
Mapper接口
@Update("UPDATE user_info set username=#{username} where id=#{id}")
void update(UserInfo userInfo);
测试用例
@Test
void update() {
UserInfo userInfo=new UserInfo();
userInfo.setId(5);
userInfo.setUsername("张三");
userInfoMapper.update(userInfo);
}
@Delete 实现删除功能
根据id删除用户信息
@Delete("DELETE from user_info where id=#{id}")
void deleteUser(Integer id);
测试用例
@Test
void deleteUser() {
userInfoMapper.deleteUser(10);
}
XML方式
MyBatisXML的方式需要以下两步:
- 配置数据库连接字符串和MyBatis
- 写持久层代码
配置连接字符串和MyBatis
spring:
application:
name: demo
datasource:
url: jdbc:mysql://127.0.0.1:3306/java113?characterEncoding=utf8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 9090
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
持久层代码
数据持久成的实现,MyBatis的固定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.UserInfoMapperXML">
其中namespace要写成mapper接口的路径
创建UserInfoXMLMapper.xml
classpath:是resource根路径
路径参考yml中的配置
使用插件MybatisX辅助开发
@Mapper
public interface UserInfoMapperXML {
List<UserInfo> selectAll();
}
可在.xml生成对应的语句
快捷键 Alt+enter
在.xml自动生成
<select id="selectAll2" resultType="com.example.demo.model.UserInfo"></select>
select查询标签:是用来执行数据库的查询操作的:
◦ id :是和Interface (接⼝)中定义的方法名称⼀样的,表示对接口的具体实现方法。
◦ resultType :是返回的数据类型,也就是开头我们定义的实体类.
增删改查
< insert>
UserInfoMapperXML接口
Integer insertUser(UserInfo userInfo);
UserInfoXMLMapper.xml实现
<insert id="insertUser">
insert into user_info(username,password,age) values (#{username},#{password},#{age})
</insert>
测试用例
@Test
void insertUser() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("username1");
userInfo.setPassword("password1");
userInfo.setAge(18);
System.out.println("影响行数:"+userInfoMapperXML.insertUser(userInfo));
}
使用@Param设置参数名称
Integer insertUser2(@Param("userInfo") UserInfo userInfo);
<insert id="insertUser2" useGeneratedKeys="true" keyProperty="id">
insert into user_info(username,password,age) values (#{userInfo.username},#{userInfo.password},#{userInfo.age})
</insert>
返回自增id
在select标签中添加参数
useGeneratedKeys="true" keyProperty="id"
测试用例
@Test
void insertUser2() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("username5");
userInfo.setPassword("password5");
userInfo.setAge(18);
Integer result = userInfoMapperXML.insertUser2(userInfo);
System.out.println("影响行数:"+result+",自增id:"+userInfo.getId());
}
< delete>
根据id删除用户信息
UserInfoMapperXML接口
Integer deleteUser(Integer id);
UserInfoXMLMapper.xml实现
<delete id="deleteUser">
delete from user_info where id=#{id}
</delete>
测试用例
@Test
void deleteUser() {
userInfoMapperXML.deleteUser(28);
}
< update>
UserInfoMapperXML接口
void updateUser(UserInfo userInfo);
UserInfoXMLMapper.xml实现
<update id="updateUser">
INSERT INTO user_info (
username,
`password`,
age,
gender,
phone)
VALUES (
#{username},
#{age},
#{gender},
#{phone})
</update>
数据库
测试用例
@Test
void updateUser() {
UserInfo userInfo = new UserInfo();
userInfo.setAge(25);
userInfo.setGender(1);
userInfo.setUsername("update111");
userInfo.setId(14);
userInfo.setPhone("2456444444");
userInfoMapperXML.updateUser(userInfo);
}
更新结果
< select>
UserInfoMapperXML接口
List<UserInfo> selectAll();
UserInfoXMLMapper.xml实现
<sql id="allColumn">
id, username, age, gender, phone, delete_flag, create_time, update_time
</sql>
<select id="selectAll" resultType="com.example.demo.model.UserInfo">
SELECT
<include refid="allColumn"></include>
FROM `user_info`
</select>
测试用例
@Test
void selectAll() {
userInfoMapperXML.selectAll().stream().forEach(x-> System.out.println(x));
}
对于字段不一致的问题和注解的解决方式一致
可以进行驼峰转换(推荐)
介绍一下结果映射
<resultMap id="BaseMap" type="com.example.demo.model.UserInfo">
<id column="id" property="id"></id>
<result column="delete_flag" property="deleteFlag"></result>
<result column="create_time" property="createTime"></result>
<result column="update_time" property="updateTime"></result>
</resultMap>
复用
<select id="queryAllUser" resultMap="BaseMap">
select id, username,`password`, age, gender, phone, delete_flag,
create_time, update_time from user_info
</select>
多表查询
建⼀张文章表,进行多表关联查询
DROP TABLE IF EXISTS articleinfo;
CREATE TABLE articleinfo (
id INT PRIMARY KEY auto_increment,
title VARCHAR ( 100 ) NOT NULL,
content TEXT NOT NULL,
uid INT NOT NULL,
delete_flag TINYINT ( 4 ) DEFAULT 0 COMMENT '0-正常, 1-删除',
create_time DATETIME DEFAULT now(),
update_time DATETIME DEFAULT now()
) DEFAULT charset 'utf8mb4';
INSERT INTO articleinfo ( title, content, uid ) VALUES ( 'Java', 'Java正文', 1
);
建立实体表ArticleInfo
@Data
public class ArticleInfo {
private Integer id;
private String title;
private String content;
private Integer uid;
private Integer deleteFlag;
private Date createTime;
private Date updateTime;
//用户相关信息
private String username;
private Integer age;
private Integer gender;
}
根据uid查询作者的名称等相关信息
ArticleInfoMapper 接口:
@Mapper
public interface ArticleInfoMapper {
@Select("SELECT ta.id,ta.title,ta.content,ta.uid,tb.username,tb.age,tb.gender " +
"FROM article_info ta LEFT JOIN user_info tb ON ta.uid = tb.id " +
"WHERE ta.id = #{id}")
ArticleInfo queryUserByUid(Integer id);
}
测试用例
@Test
void queryUserByUid() {
articleInfoMapper.queryUserByUid(1);
}
${ } 字符串替换
不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:
ORDER BY ${columnName}
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。 举个例子,如果你想 select 一个表任意一列的数据时
@Select("select * from user_info where ${column} = #{value}")
UserInfo findByColumn(@Param("column") String column, @Param("value") String value);
这种方式也同样适用于替换表名的情况。
注意:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。
对于String类型的参数
@Select("select* from user_info where usename=${username} and gender=#{gender}")
List<UserInfo> selectAllBynameAndGender(String username,Integer gender);
字符串作为参数时,需要添加引号 ‘’ ,使用 ${} 不会拼接引号 ‘’ ,导致程序报错
@Select("select* from user_info where username='${username}' and gender=#{gender}")
List<UserInfo> selectAllBynameAndGender(String username,Integer gender);
#{} 使用的是预编译SQL,通过? 占位的方式,提前对SQL进⾏编译,然后把参数填充到SQL语句中.#{} 会根据参数类型,自动拼接引号’’ .
${}会直接进行字符替换,对SQL进行编译.如果参数为字符串,需要加上引号 ’ ’ .
SQL注入
SQL注入:是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法。
由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加⼀些SQL关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。
测试用例
@Test
void selectAllBynameAndGender() {
System.out.println(userInfoMapper.selectAllBynameAndGender("'or 1='1", 0));
}
结果依然被正确查询出来了,其中参数or被当做了SQL语句的⼀部分
SQL注入是⼀种非常常见的数据库攻击手段, SQL注入漏洞也是网络世界中最普遍的漏洞之一.
如果发生在用户登录的场景中,密码输入为 ’ or 1='1 ,就可能完成登录(不是一定会发生的场景,需要看登录代码如何写)
sort
${}的使用场景
排序
@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} 也不加引号
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);
数据库连接池
我们使用了数据库连接池技术,避免频繁的创建连接,销毁连接
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,而不是再重新建立⼀个
没有使用数据库连接池的情况:每次执行SQL语句,要先创建⼀个新的连接对象,然后执行SQL语句, SQL语句执行完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接比较消耗资源
使用数据库连接池的情况:程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,用户请求数据库连接池,会从数据库连接池中获取Connection对象,然后执行SQL,SQL语句执行完,再把Connection归还给连接池.
优点:
- 减少了网络开销
- 资源重用
- 提升了系统的性能
使用
常见的数据库连接池:
• C3P0
• DBCP
• Druid
• Hikari
-
Hikari:SpringBoot默认使用的数据库连接池
-
Druid
如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引相关依赖即可
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.21</version>
</dependency>
参考官方地址:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter
• Druid连接池是阿⾥巴巴开源的数据库连接池项目
• 功能强大,性能优秀,是Java语言最好的数据库连接池之⼀
#{}和${}区别
- #{}:预编译处理, ${}:字符直接替换
- #{}可以防止SQL注入,${}存在SQL注入的风险,查询语句中,可以使用#{},推荐使用#{}
- 但是⼀些场景,#{}不能完成,比如排序功能,表名,字段名作为参数时,这些情况需要使用${}
- 模糊查询虽然${}可以完成,但因为存在SQL注⼊的问题,所以通常使用mysql内置函数concat来完成