news 2026/10/13 12:54:37

Mybatis(一)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mybatis(一)

一,JDBC的操作流程

1. 创建数据库连接池DataSource

2. 通过DataSource获取数据库连接Connection

3. 编写要执⾏带?占位符的SQL语句

4. 通过Connection及SQL创建操作命令对象Statement

5. 替换占位符:指定要替换的数据库字段类型,占位符索引及要替换的值

6. 使⽤Statement执⾏SQL语句

7. 查询操作:返回结果集ResultSet,更新操作:返回更新的数量

8. 处理结果集

9. 释放资源

在以上的步骤中,1,2,4,6,7,9可以提取出来让mybatic去做

二,mybatis

• MyBatis是⼀款优秀的持久层框架,用于简化JDBC的开发

• 持久层:指的就是持久化操作的层,通常指数据访问层(dao),是⽤来操作数据库的

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

2.1 MyBatis入门

Mybatis操作数据库的步骤:

1. 准备工作(创建springboot⼯程、数据库表准备、实体类)

2. 引⼊Mybatis的相关依赖,配置Mybatis(数据库连接信息)

3. 编写SQL语句(注解/XML)

4. 测试

Mybatis的持久层接⼝规范⼀般都叫XxxMapper

@Mapper注解:表⽰是MyBatis中的Mapper接⼝

• 程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象),并给交Spring的IOC容器管理

• @Select注解:代表的就是select查询,也就是注解对应⽅法的具体实现内容.

测试类上添加了注解@SpringBootTest,该测试类在运⾏时,就会⾃动加载Spring的运⾏环境. 我们通过@Autowired这个注解,注⼊我们要测试的类,就可以开始进⾏测试了

@Select("select * from user_info where username=#{username} and password =#{password}") List<UserInfo> getListByNameAndPassword(String username,String password);

在这段代码中,如果改成

@Select("select * from user_info where username=#{username1} and password =#{password1}") List<UserInfo> getListByNameAndPassword(String username,String password);

就会报错,如下:

[password, param1, username, param2] 是框架自动帮我们生成的参数,其中框架会生成参数名对应的参数和按照参数顺序生成的param1,param2........

@Select("select * from user_info where username=#{param1} and password =#{param2}") List<UserInfo> getListByNameAndPassword(String username,String password);

当改成这样的时候,运行成功

生成的这四个参数,password, username是无序的。param1, param2是有序的

三,MyBatis的基础操作

3.1 打印⽇志

在Mybatis当中我们可以借助⽇志,查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果

在配置⽂件中进⾏配置即可

3.2 参数传递

需求:查找id=4的⽤⼾,对应的SQL就是:select*fromuser_infowhereid=4

@Select("select * from user_info where id =4") UserInfo getUserInfoById();

但是这样的话, 只能查找id=4的数据,所以SQL语句中的id值不能写成固定数值,需要变为动态的数值

解决⽅案:在queryById⽅法中添加⼀个参数(id),将⽅法中的参数,传给SQL语句

使⽤ #{} 的⽅式获取⽅法中的参数

@Select("select * from user_info where id = #{id}") UserInfo getUserInfoById(Integer id);

如果mapper接⼝⽅法形参只有⼀个普通类型的参数,#{…}⾥⾯的属性名可以随便写,如:#{id}、# {value}。建议和参数名保持⼀致

也可以通过 @Param ,设置参数的别名,如果使⽤ @Param 设置别名,#{...}⾥⾯的属性名必须和 @Param 设置的⼀样

@Select("select * from user_info where username=#{username} and password =#{password}") List<UserInfo> getListByNameAndPassword2(@Param("username1") String username, @Param("password1") String password);

3.3 增(Insert)

@Insert("insert into user_info (`username`,`password`, `age`) values(#{username},#{password}, #{age})") Integer insertUser(UserInfo userInfo);

sql语句:

insert into user_info (username, `password`, age, gender) values ("zhaoliu","zhaoliu",19,1)

把SQL中的常量替换为动态的参数

Mapper接⼝:

@Insert("insert into user_info (`username`,`password`, `age`) values(#{username},#{password}, #{age})") Integer insertUser(UserInfo userInfo);

直接使⽤UserInfo对象的属性名来获取参数

测试代码:

@Test void insertUser() { UserInfo userInfo = new UserInfo("zhaoliu", "zhaoliu", 17); Integer i = userInfoMapper.insertUser(userInfo); System.out.println("插入行数:"+i + ", 自增Id:"+ userInfo.getId()); }

如果设置了 @Param 属性,#{...}需要使⽤参数.属性来获取

@Insert("insert into user_info (username, `password`, age, gender, phone) values (#{userInfo.username},#{userInfo.password},#{userInfo.age},# {userInfo.gender},#{userInfo.phone})") Integer insert(@Param("userInfo") UserInfo userInfo);

返回主键

Insert 语句默认返回的是受影响的⾏数

但有些情况下,数据插⼊之后,还需要有后续的关联操作,需要获取到新插⼊数据的id

如果想要拿到⾃增id,需要在Mapper接⼝的⽅法上添加⼀个Options的注解

@Options(useGeneratedKeys = true, keyProperty = "id") @Insert("insert into user_info (`username`,`password`, `age`) values(#{username},#{password}, #{age})") Integer insertUser(UserInfo userInfo);

• useGeneratedKeys:这会令MyBatis使⽤JDBC的getGeneratedKeys⽅法来取出由数据库内 部⽣成的主键(⽐如:像MySQL和SQLServer这样的关系型数据库管理系统的⾃动递增字 段),默认值:false.

• keyProperty:指定能够唯⼀识别对象的属性,MyBatis会使⽤getGeneratedKeys的返回值或 insert 语句的selectKey⼦元素设置它的值,默认值:未设置(unset)

注意:设置 useGeneratedKeys=true 之后,⽅法返回值依然是受影响的⾏数,⾃增 id 会设置在上述

keyProperty 指定的属性中.

3.4 删(Delete)

Mapper接⼝

@Delete("delete from user_info where id =#{id}") Integer deleteUserById(Integer id);

3.5 改(Update)

@Update("update user_info set username= #{username} where id = #{id}") Integer updateUserById(UserInfo userInfo);
@Test void updateUserById() { UserInfo userInfo = new UserInfo(); userInfo.setId(5); userInfo.setUsername("ll"); System.out.println(userInfoMapper.updateUserById(userInfo)); }
@Update("update user_info set username= #{username} where id = #{id}") Integer updateUserById2(@Param("userInfo") UserInfo userInfo);

当加上注解的时候会报错。

修正:

@Update("update user_info set username= #{userInfo.username} where id = #{userInfo.id}") Integer updateUserById2(@Param("userInfo") UserInfo userInfo);

3.6 查(Select)

查询时发现,有⼏个字段是没有赋值的,只有Java对象属性和数据库字段⼀模⼀样时,才会进 ⾏赋值

当我们多查询一些数据的时候:

@Select("select id, username, `password`, age, gender, phone, delete_flag, create_time, update_time from user_info") List<UserInfo> queryAllUser();

有很多null

从运⾏结果上可以看到,我们SQL语句中,查询了delete_flag,create_time,update_time,但是这⼏个 属性却没有赋值

MyBatis 会根据⽅法的返回结果进⾏赋值.

⽅法⽤对象UserInfo接收返回结果,MySQL查询出来数据为⼀条,就会⾃动赋值给对象.

⽅法⽤List接收返回结果,MySQL查询出来数据为⼀条或多条时,也会⾃动赋值给List.

但如果MySQL查询返回多条,但是⽅法使⽤UserInfo接收,MyBatis执⾏就会报错

原因分析:

当⾃动映射查询结果时,MyBatis会获取结果中返回的列名并在Java类中查找相同名字的属性(忽略⼤⼩写)。这意味着如果发现了ID列和id属性,MyBatis会将列ID的值赋给id属性

解决办法:

1. 起别名 2. 结果映射 3. 开启驼峰命名

3.6.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") List<UserInfo> getList2();

3.6.2 结果映射

@Results({ @Result(column = "delete_flag", property = "deleteFlag"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime"), }) @Select("select * from user_info") List<UserInfo> getList2();

其中,results的源码为:

如果其他SQL,也希望可以复⽤这个映射关系,可以给这个Results定义⼀个名称

@Results(id = "BaseMap", value ={ @Result(column = "delete_flag", property = "deleteFlag"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime"), }) @Select("select * from user_info") List<UserInfo> getList2();

这样的话,不仅getList2可以用这个results,getList也可以使用这个results

@ResultMap("BaseMap") @Select("select * from user_info") List<UserInfo> getList();

使⽤ id 属性给该 Results 定义别名,使⽤ @ResultMap 注解来复⽤其他定义的 ResultMap

3.6.3 开启驼峰命名(推荐)

通常数据库列使用蛇形命名法进行命名(下划线分割各个单词),而Java属性⼀般遵循驼峰命名法约定. 为了在这两种命名方式之间启用自动映射,需要将mapUnderscoreToCamelCase 设置为true。

四,MyBatis XML配置文件

Mybatis的开发有两种方式:

1. 注解

2. XML

使用Mybatis的注解方式,主要是来完成⼀些简单的增删改查功能。如果需要实现复杂的SQL功能,建议使用XML来配置映射语句,也就是将SQL语句写在XML配置⽂件中

MyBatis XML的方式需要以下两步:

1. 配置数据库连接字符串和MyBatis

2. 写持久层代码

XML的配置文件写法:

1)添加依赖(mybatis和mysql)

2)配置文件(数据库信息,xml文件地址)

3)写接口文件

4)写xml的实现

其中resultType为返回结果的类型。需要注意的是如果返回的为集合时,resultType设置为集合中元素的类型

4.1 配置连接字符串和MyBatis

# 数据库连接配置 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true username: root password: '123456' driver-class-name: com.mysql.cj.jdbc.Driver mybatis: configuration: # 配置打印 MyBatis日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl map-underscore-to-camel-case: true #配置驼峰⾃动转换 mapper-locations: classpath:/mapper/**.xml

4.2 写持久层代码

持久层代码分两部分

1. ⽅法定义Interface

2. ⽅法实现:XXX.xml

<mapper>标签:需要指定 namespace 属性,表⽰命名空间,值为mapper接⼝的全限定名,包括全包名.类名

<select>查询标签:是⽤来执⾏数据库的查询操作的

id :是和 Interface (接⼝)中定义的⽅法名称⼀样的,表⽰对接⼝的具体实现⽅法。

resultType :是返回的数据类型,也就是开头我们定义的实体类.

4.3 增删改查操作

4.3.4 查(Select)

同样的,使⽤XML的⽅式进⾏查询,也存在数据封装的问题 我们把SQL语句进⾏简单修改,查询更多的字段内容,有的属性会没有被赋值

解决办法和注解类似:

1. 起别名 2. 结果映射 3. 开启驼峰命名

<resultMap id="BaseMap" type="com.luoyaoyun.mybatisdemo.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="getList" resultType="com.luoyaoyun.mybatisdemo.model.UserInfo">--> <select id="getList" resultMap="BaseMap"> select * from user_info </select>

五,其他查询操作

5.1 多表查询

多表查询和单表查询类似,只是SQL不同⽽已

5.1.1 准备⼯作

建⼀张⽂章表,进⾏多表关联查询. ⽂章表的uid,对应⽤⼾表的id

5.1.2 数据查询

对应Model:

@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; private String phone; }
@Mapper public interface ArticleInfoMapper { ArticleInfo getArticleInfoById(Integer articleId); }
<?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.luoyaoyun.mybatisdemo.mapper.ArticleInfoMapper"> <select id="getArticleInfoById" resultType="com.luoyaoyun.mybatisdemo.model.ArticleInfo"> <!-- select * from article_info where id = #{articleId}--> SELECT ta.* , tb.username, tb.age, tb.gender, tb.phone FROM article_info ta left join user_info tb on ta.uid= tb.id where ta.id=#{articleId} </select> </mapper>

Mybatis 不分单表还是多表,主要就是三部分:SQL,映射关系和实体类 通过映射关系,把SQL运⾏结果和实体类关联起来

多表查询的缺点:

1)性能低

2)会产生慢sql

所以会进行优化,以上述多表查询的例子来看,可以在dao层中实现 ta中id=1的单表查询,得到了uid之后,再实现ta中uid=tb的id的单表查询

5.1.3 一对多

需求:根据用户ID查询该用户的文章列表

SQL

SELECT ta.id AS user_id, ta.username, ta.age, ta.gender, ta.phone, tb.id AS article_id, tb.title, tb.content FROM user_info ta LEFT JOIN article_info tb ON ta.id = tb.uid WHERE ta.id = 1

补充实体类UserInfo,添加articleInfoList

@NoArgsConstructor @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; private List<ArticleInfo> articleInfoList; }

接⼝定义和接⼝实现

5.2 #{} 和${}

1. 先看Interger类型的参数

@Select("select * from user_info where id = #{id}") UserInfo getUserInfoById(Integer id);

我们输入的参数并没有在后面拼接,id的值是使用 ? 进行占位. 这种SQL我们称之为"预编译SQL"

把 #{} 改成 ${} 再观察打印的⽇志:

可以看到,这次的参数是直接拼接在SQL语句中了.

2. 接下来我们再看String类型的参数

@Select("select username, 'password', age, gender, phone from user_info where username= ${name}") UserInfo queryByName(String name);

可以看到,这次的参数依然是直接拼接在SQL语句中了,但是字符串作为参数时,需要添加引号 ‘ ’ ,使用${},不会拼接引号 ‘ ’ ,导致程序错误

修正代码如下:

@Select("select username, 'password', age, gender, phone from user_info where username= '${name}'") UserInfo queryByName(String name);

从上面两个例子可以看出:

#{} 使用的是预编译SQL,通过 ? 占位的方式,提前对SQL进⾏编译,然后把参数填充到SQL语句 中。#{} 会根据参数类型,自动拼接引号 ' '

${} 会直接进行字符替换,⼀起对SQL进行编译,如果参数为字符串,需要加上引号 ' '

参数为数字类型时,也可以加上,查询结果不变,但是可能会导致索引失效,性能下降.

5.2.2 #{} 和${}区别

#{} 和${} 的区别就是预编译SQL和即时SQL的区别

当客⼾发送⼀条SQL语句给服务器后,⼤致流程如下:

1. 解析语法和语义,校验SQL语句是否正确

2. 优化SQL语句,制定执⾏计划

3. 执⾏并返回结果 ⼀条SQL如果⾛上述流程处理,我们称之为ImmediateStatements(即时SQL)

1. 性能更高

绝大多数情况下,某⼀条SQL语句可能会被反复调⽤执⾏,或者每次执⾏的时候只有个别的值不同(⽐如select的where⼦句值不同,update的set⼦句值不同,insert的values值不同).如果每次都需要 经过上⾯的语法解析,SQL优化、SQL编译等,则效率就明显不⾏了.

预编译SQL,编译⼀次之后会将编译后的SQL语句缓存起来,后⾯再次执⾏这条语句时,不会再次编译 (只是输⼊的参数不同),省去了解析优化等过程,以此来提⾼效率

2. 更安全(防止SQL注入)

SQL注⼊:是通过操作输⼊的数据来修改事先定义好的SQL语句,以达到执⾏代码对服务器进⾏攻击的方法。

由于没有对⽤⼾输⼊进⾏充分检查,⽽SQL⼜是拼接⽽成,在⽤⼾输⼊参数时,在参数中添加⼀些 SQL关键字,达到改变SQL运⾏结果的⽬的,也可以完成恶意攻击。

sql 注⼊代码: ' or 1='1

5.3 排序功能

从上⾯的例⼦中,可以得出结论:${}会有SQL注⼊的⻛险,所以我们尽量使⽤#{}完成查询

既然如此,是不是${}就没有存在的必要性了呢? 当然不是.

接下来看下${}的使⽤场景:

@Select("select * from user_info order by id #{order}") List<UserInfo> getListByOrder(String order);

此处报错是因为desc不能加单引号,但是当传入string类型时,#{}会自动加单引号

所以此处只能用${},同时为了防止sql注入问题,可以限制为选项,从而不让自由输入

除此之外,还有表名作为参数时,也只能使用 ${},不能加引号的都只能用${}

5.4 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归还给连接池

优点: 1. 减少了⽹络开销 2. 资源重⽤ 3. 提升了系统的性能

6.2 使用

常⻅的数据库连接池:

• C3P0

• DBCP

• Druid

• Hikari

1. Hikari : SpringBoot默认使⽤的数据库连接池

2. Druid

如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引⼊相关依赖即可

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-3-starter</artifactId> <version>1.2.21</version> </dependency>

如果SpringBoot版本为2.X,使⽤druid-spring-boot-starter依赖

<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.17</version> </dependency>
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 12:54:12

137B编码模型PC端落地:推理优化与Agent权限防线设计

把137B这个量级的编码模型搬到PC上跑&#xff0c;放在两年前几乎是想都不敢想的事。当时大家还在为7B、13B模型怎么塞进消费级显卡发愁&#xff0c;现在画风突变&#xff0c;头部厂商直接把百亿参数级别的编码大模型下放到个人电脑&#xff0c;还兼顾Agent形态的工具调用能力。…

作者头像 李华
网站建设 2026/10/13 12:52:22

基于 FlinkSQL + Paimon + Doris 的车联网流批一体湖仓实践 —— 第 16 章 性能优化清单与治理策略

基于 FlinkSQL + Paimon + Doris 的车联网流批一体湖仓实践 —— 第 16 章 性能优化清单与治理策略 系列定位:本系列以车联网 TSP 行程主题数仓为真实生产案例,完整复盘一套 Flink 1.20 + Paimon 1.4 + Doris 4.1 流批一体湖仓的架构设计、建设过程、上线部署、性能优化与踩坑…

作者头像 李华
网站建设 2026/10/13 12:52:22

第二章:5、RAG进阶优化

一、混合检索&#xff08;向量 关键词&#xff09;1. 纯向量检索的局限语义相近但答案靠后&#xff1a;向量相似度高不代表文档真正包含答案。精确关键词匹配弱&#xff1a;用户搜索特定术语时&#xff0c;向量检索可能漏掉精确匹配的文档。多路召回更可靠&#xff1a;结合向量…

作者头像 李华
网站建设 2026/10/13 12:52:19

POC测试评分表:技术选型的防错机制与验证闭环

简介&#xff1a;本资源是一份标准化的POC测试评分表模板文档&#xff0c;面向企业IT架构师、系统集成工程师、业务需求分析师及测试团队成员&#xff0c;用于在概念验证阶段科学评估待选系统或解决方案的功能完备性与接口兼容性。文档以Word格式&#xff08;.doc&#xff09;单…

作者头像 李华