MyBatis(前身为iBATIS)是一种Java持久层框架,用于简化数据库访问的开发。它提供了一种将SQL语句与Java代码解耦的方式,使得数据库操作更加灵活和易于维护。
MyBatis的核心思想是通过XML或注解配置SQL映射关系,将SQL语句与Java方法进行关联。开发人员只需定义SQL语句,并将其映射到相应的Java方法上,MyBatis会自动执行SQL并将结果映射到Java对象中。
以下是MyBatis的一些主要特点:
简化数据库操作: MyBatis提供了简洁的API和灵活的配置方式,使得数据库操作变得更加简单和直观。
强大的SQL支持: MyBatis支持复杂的SQL语句,包括动态SQL、参数映射、嵌套查询等,可以满足各种数据库操作的需求。
性能优化: MyBatis具有良好的性能,可以通过缓存、批量操作等手段提升数据库操作效率。
与现有代码集成: MyBatis不需要引入复杂的对象关系映射(ORM),可以与现有的Java代码和数据库结构无缝集成。
可扩展性: MyBatis允许开发人员编写自定义的类型处理器、插件等,以满足特定的需求。
Mybatis操作数据库的步骤
-- 创建数据库
DROP DATABASE IF EXISTS mybatis_test;
CREATE DATABASE mybatis_test DEFAULT CHARACTER SET utf8mb4;
-- 使⽤数据数据
USE mybatis_test;
-- 创建表[⽤⼾表]
DROP TABLE IF EXISTS userinfo;
CREATE TABLE `userinfo` (
`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(),
PRIMARY KEY ( `id` )
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4;
-- 添加⽤⼾信息
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'admin', 'admin', 18, 1, '18612340001' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'zhangsan', 'zhangsan', 18, 1, '18612340002' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'lisi', 'lisi', 18, 1, '18612340003' );
INSERT INTO mybatis_test.userinfo ( username, `password`, age, gender, phone )
VALUES ( 'wangwu', 'wangwu', 18, 1, '18612340004' );
创建实体类
import lombok.Data;
import java.util.Date;
@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;
}
Mybatis要连接数据库 需要配置相关参数
- Mysql驱动类
- 登录名
- 密码
- 数据库连接字符串
配置application.yml文件
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.*;
import java.util.List;
//@Mapper注解; 表示是mybatis中的mapper接口
//程序运行时 框架会自动生成接口的实现类对象 并交给springioc容器管理
@Mapper
public interface UserInfoMapper {
//查询所有用户
@Select("select * from userinfo")
List<UserInfo> queryAllUser();
}
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
//添加springboot注解 该测试类在运行时 就会自动加载spring的运行环境
@SpringBootTest
@Slf4j
class UserInfoMapperTest {
//注入要测试的类
@Autowired
private UserInfoMapper userInfoMapper;
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
}
在Mybatis当中我们可以借助⽇志, 查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果
在配置⽂件中进⾏配置即可
mybatis:
configuration: # 配置打印 MyBatis日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
需求:查找id = 4 的用户对应的SQL就是: select * from userinfo where id=4
但是这样的话, 只能查找id=4 的数据, 所以SQL语句中的id值不能写成固定数值,需要变为动态的数值
解决⽅案:在queryById⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句
使⽤ #{} 的⽅式获取⽅法中的参数
@Select("select username, `password`, age, gender, phone from userinfo where id=#{id}")
UserInfo queryById(Integer id);
如果mapper接⼝⽅法形参只有⼀个普通类型的参数,#{…} ⾥⾯的属性名可以随便写,如:#{id}、#{value}。建议和参数名保持⼀致
测试用例
void queryUserById() {
List<UserInfo> userInfo = userInfoMapper.queryUserById(4);
System.out.println(userInfo);
}
sql语句
@Insert("insert into userinfo (username,password,age,gender,phone) " +
"values(#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
//把sql中的常量替换为动态的参数
测试代码
直接使用UserInfo对象的属性名来获取参数
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangliu");
userInfo.setPassword("123456789");
userInfo.setAge(18);
userInfo.setGender(1);
userInfo.setPhone("15968456325");
Integer result = userInfoMapper.insert(userInfo);
System.out.println("添加数据条数:"+result + ", 主键id:"+userInfo.getId());
}
如果设置了 @Param 属性, #{…} 需要使⽤ 参数.属性 来获取
@Insert("insert into userinfo (username,password,age,gender,phone) " +
"values(#{userinfo.username},#{userinfo.password},#{userinfo.age},#{userinfo.gender},#{userinfo.phone})")
Integer insert2(@Param("userinfo") UserInfo userInfo);
Insert 语句默认返回的是 受影响的⾏数
但有些情况下, 数据插⼊之后, 还需要有后续的关联操作, 需要获取到新插⼊数据的id
如果想要拿到⾃增id, 需要在Mapper接⼝的⽅法上添加⼀个Options的注解
@Options(useGeneratedKeys = true, keyProperty = "id")
@Insert("insert into userinfo (username,password,age,gender,phone) " +
"values(#{username},#{password},#{age},#{gender},#{phone})")
Integer insert(UserInfo userInfo);
useGeneratedKeys:这会令 MyBatis 使⽤ JDBC 的 getGeneratedKeys ⽅法来取出由数据库内部⽣成的主键(⽐如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的⾃动递增字段),默认值:false
keyProperty:指定能够唯⼀识别对象的属性,MyBatis 会使⽤ getGeneratedKeys 的返回值或insert 语句的 selectKey ⼦元素设置它的值,默认值:未设置(unset)
测试数据
@Test
void insert() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("zhangliu");
userInfo.setPassword("123456789");
userInfo.setAge(18);
userInfo.setGender(1);
userInfo.setPhone("15968456325");
Integer result = userInfoMapper.insert(userInfo);
System.out.println("添加数据条数:"+result + ", 主键id:"+userInfo.getId());
}
sql
@Delete("delete from userinfo where id = #{id}")
//根据id删除
Integer delete(Integer id);
测试代码
@Test
void delete() {
Integer result = userInfoMapper.delete(5);
}
sql
@Update("update userinfo set username=#{username} where id=#{id}")
Integer update(UserInfo userInfo);
}
测试代码
@Test
void update() {
UserInfo userInfo = new UserInfo();
userInfo.setUsername("wuqi");
userInfo.setId(1);
Integer result= userInfoMapper.update(userInfo);
if (result>0){
log.info("数据修改成功");
}
Integer update = userInfoMapper.update(userInfo);
}
@Select("select * from userinfo where id = #{id}")
List<UserInfo> queryUserById(Integer id);
测试代码
@Test
void queryAllUser() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUser();
System.out.println(userInfoList);
}
Mybatis的开发有两种⽅式:
使⽤Mybatis的注解⽅式,主要是来完成⼀些简单的增删改查功能. 如果需要实现复杂的SQL功能,建议使⽤XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中.
MyBatis XML的⽅式需要以下两步:
此步骤需要进⾏两项设置,数据库连接字符串设置和 MyBatis 的 XML ⽂件配置。
如果是application.yml⽂件, 配置内容如下:
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 配置 mybatis xml 的⽂件路径,在 resources/mapper 创建所有表的 xml ⽂件
mybatis:
mapper-locations: classpath:mapper/**Mapper.xml
持久层代码分两部分
package com.example.mybatisdemo.mapper;
import com.example.mybatisdemo.model.UserInfo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserInfoXMLMapper {
//查
List<UserInfo> queryAllUser();
//增
Integer insertUser(UserInfo userInfo);
//删
Integer deleteUser(Integer id);
//改
Integer updateUser(UserInfo userInfo);
}
数据持久成的实现,MyBatis 的固定 xml 格式:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mybatisdemo.mapper.UserInfoXMLMapper">
mapper>
UserInfoMapper接⼝:
Integer insertUser(UserInfo userInfo);
xml
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into userinfo (username,password,age,gender,phone)
values(#{username},#{password},#{age},#{gender},#{phone})
insert>
mapper接口
Integer deleteUser(Integer id);
xml
<delete id="deleteUser">
delete from userinfo where id = #{id}
delete>
mapper接口
Integer updateUser(UserInfo userInfo);
xml
<update id="updateUser">
update userinfo set username = #{username} where id = #{id}
update>
mapper接口
List<UserInfo> queryAllUser();
<select id="queryAllUser" resultType="com.example.mybatisdemo.model.UserInfo">
select * from userinfo
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 );
package com.example.mybatisdemo.model;
import lombok.Data;
import java.util.Date;
@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;
}
要求 根据uid查询作者的名称等相关信息
SELECT
ta.id,
ta.title,
ta.content,
ta.uid,
tb.username,
tb.age,
tb.gender
FROM
articleinfo ta
LEFT JOIN userinfo tb ON ta.uid = tb.id
WHERE
ta.id =1
先补充查询结果的实体类
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;
}
mapper接口
@Mapper
public interface ArticleInfoMapper {
//多表查询
@Select("select ta.*, tb.username,tb.age,tb.gender from articleinfo ta" +
" LEFT JOIN userinfo tb on ta.uid = tb.id" +
" where ta.id=#{id}")
ArticleInfo queryUserById(Integer id);
}
我们发现输出的sql语句 参数并没有直接在后面拼接 id的值是使用?进行占位 这种sql我们成为预编译sql
此时把#{}改成${} 再观察打印的日志
此时我们输入的参数是直接拼接在sql中了
@Select("select * from userinfo where username = #{username}")
List<UserInfo> queryUserByName(String username);
我们再把#{}修改为${}
此时我们发现 这次的参数直接拼接在sql中 但是我们以字符串为参数时,需要添加引号,但是使用 ${} 不会拼接引号 导致程序报错
此时我们需要手动添加引号
在MyBatis中,#{}和${}都是用于在SQL语句中进行参数的传递,但它们之间有一些重要的区别。
#{}用于预编译参数,MyBatis在将SQL语句发送到数据库之前,会使用PreparedStatement参数化查询的方式来处理#{}中的内容,这样可以有效防止SQL注入攻击,并且能够正确地处理特殊字符转义。因此,建议在编写SQL语句时使用#{}来引用参数。
${}则是直接进行字符串替换,不会进行预编译,而是将其中的内容直接拼接到sql语句中。这种方式可能会存在sql注入的风险,因此不建议在编写sql语句时使用则是直接进行字符串替换,不会进行预编译,而是将其中的内容直接拼接到SQL语句中。这种方式可能会存在SQL注入的风险,因此不建议在编写SQL语句时使用{}来引用参数,除非你非常清楚自己在做什么并且能够确保参数的安全性。
因此,总的来说,推荐在MyBatis中使用#{}来引用参数,以确保SQL的安全性和可读性。
从上⾯的例⼦中, 可以得出结论: ${} 会有SQL注⼊的⻛险, 所以我们尽量使⽤#{}完成查询
既然如此, 是不是 ${} 就没有存在的必要性了呢?
当然不是.
比如我们要实现一个排序功能
mapper
@Select("select * from userinfo order by id ${sort}")
List<UserInfo> queryAllUserBySort(String sort);
测试代码
@Test
void queryAllUserBySort() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUserBySort("desc");
System.out.println(userInfoList);
}
此处报错的原因就是 在使用#{}时 给desc自动加上了引号 但是实际上此处并不需要在此处加引号 导致了报错
#{} 会根据参数类型判断是否拼接引号 ''如果参数类型为String, 就会加上 引号
除此之外, 还有表名作为参数时, 也只能使⽤ ${}
like 使⽤ #{} 报错
mapper
@Select("select * from userinfo where username like '%#{key}%'")
List<UserInfo> queryAllUserByLike(String key);
测试代码
@Test
void queryAllUserByLike() {
List<UserInfo> userInfoList = userInfoMapper.queryAllUserByLike("x");
System.out.println(userInfoList);
}
我们发现此时还是由于引号的问题 导致sql语句不能够正常表达
此时我们可以使用KaTeX parse error: Expected 'EOF', got '#' at position 6: {}替换 #̲{} , 但是此时又引入了sq…{}
此时我们的解决方法 可以使用mysql内置函数concat()来处理
此时就可以解决sql注入问题 还能够完成模糊查询的需求
在上⾯Mybatis的讲解中, 我们使⽤了数据库连接池技术, 避免频繁的创建连接, 销毁连接下⾯我们来了解下数据库连接池
数据库连接池负责分配、管理和释放数据库连接,它允许应⽤程序重复使⽤⼀个现有的数据库连接,⽽不是再重新建⽴⼀个.
没有使⽤数据库连接池的情况: 每次执⾏SQL语句, 要先创建⼀个新的连接对象, 然后执⾏SQL语句, SQL语句执⾏完, 再关闭连接对象释放资源. 这种重复的创建连接, 销毁连接⽐较消耗资源
使⽤数据库连接池的情况: 程序启动时, 会在数据库连接池中创建⼀定数量的Connection对象, 当客⼾请求数据库连接池, 会从数据库连接池中获取Connection对象, 然后执⾏SQL, SQL语句执⾏完, 再把Connection归还给连接池
常⻅的数据库连接池:
⽬前⽐较流⾏的是 Hikari, Druid
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.17version>
dependency>
动态 SQL 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接
在注册用户的时候 可能会有这样一个问题 在注册的过程中 会出现必填字段和非必填字段,如果再添加用户的时候有不确定的字段传入,程序该如何实现呢?
此时就应该使用动态标签来判断了 ,例如添加的时候gender为非必填字段
xml方式实现
<insert id="insertUserByCondition">
insert into userinfo(
`username`,
`password`,
`age`,
<if test="gender != null">
gender,
if>
phone)
values (
#{username},
#{password},
#{age},
<if test="gender != null">
#{gender},
if>
#{phone})
insert>
也可以使用注解的方式(不推荐)
@Insert("")
Integer insertUserByCondition(UserInfo userInfo);
之前的插⼊⽤⼾功能,只是有⼀个 gender 字段可能是选填项,如果有多个字段,⼀般考虑使⽤标签结合标签,对多个字段都采取动态⽣成的⽅式。
标签中有如下属性:
<insert id="insertUserByCondition">
insert into userinfo
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
`username`,
if>
<if test="password != null">
`password`,
if>
<if test="age != null">
`age`,
if>
<if test="gender != null">
`gender`,
if>
<if test="phone != null">
`phone`
if>
trim>
values
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="username != null">
#{username},
if>
<if test="password != null">
#{password},
if>
<if test="age != null">
#{age},
if>
<if test="gender != null">
#{gender},
if>
<if test="phone != null">
#{phone}
if>
trim>
insert>
在以上 sql 动态解析时,会将第⼀个 部分做如下处理:
• 基于 prefix 配置,开始部分加上 (
• 基于 suffix 配置,结束部分加上 )
• 多个 组织的语句都以 , 结尾,在最后拼接好的字符串还会以 , 结尾,会基于suffixOverrides 配置去掉最后⼀个,
需求: 传⼊的⽤⼾对象,根据属性做where条件查询,⽤⼾对象中属性不为 null 的,都为查询条件. 如username 为 “a”,则查询条件为 where username=“a”
sql
SELECT
*
FROM
userinfo
WHERE
age = 18
AND gender = 1
AND delete_flag =0
xml
<select id="queryByCondition">
select *
from userinfo
<where>
<if test="age != null">
and age = #{age}
if>
<if test="gender != null">
and gender = #{gender}
if>
<if test="deleteFlag != null">
and delete_flag = #{deleteFlag}
if>
where>
select>
需求: 根据传⼊的⽤⼾对象属性来更新⽤⼾数据,可以使⽤标签来指定动态内容.
xml
<update id="updateUserByCondition">
update userinfo
<set>
<if test="username != null">
username = #{username},
if>
<if test="age != null">
age = #{age},
if>
<if test="deleteFlag != null">
delete_flag = #{deleteFlag},
if>
set>
where id = #{id}
update>
mapper接口
Integer updateUserByCondition(UserInfo userInfo);
测试代码
@Test
void updateUserByCondition() {
UserInfo userInfo = new UserInfo();
userInfo.setId(1);
userInfo.setUsername("zhangsan");
Integer integer = userInfoXMLMapper.updateUserByCondition(userInfo);
}
对集合进⾏遍历时可以使⽤该标签。标签有如下属性:
• collection:绑定⽅法参数中的集合,如 List,Set,Map或数组对象
• item:遍历时的每⼀个对象
• open:语句块开头的字符串
• close:语句块结束的字符串
• separator:每次遍历之间间隔的字符串
需求: 根据多个userid, 删除⽤⼾数据
xml
<delete id="deleteByIds">
delete from userinfo
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
foreach>
delete>
接口
void deleteByIds(List<Integer> ids);
测试代码
@Test
void deleteByIds() {
userInfoXMLMapper.deleteByIds(Arrays.asList(1,2,3,4));
}
问题分析:
在xml映射⽂件中配置的SQL,有时可能会存在很多重复的⽚段,此时就会存在很多冗余的代码
我们可以对重复的代码⽚段进⾏抽取,将其通过 标签封装到⼀个SQL⽚段,然后再通过 标签进⾏引⽤
• :定义可重⽤的SQL⽚段
• :通过属性refid,指定包含的SQL⽚段
<sql id="allColumn">
id, username, age, gender, phone, delete_flag, create_time, update_time
sql>
通过 标签在原来抽取的地⽅进⾏引⽤。操作如下:
<select id="queryAllUser" resultMap="BaseMap">
select
<include refid="allColumn">include>
from userinfo
select>