Mybatis(一)

📅 发布时间:2026/10/13 12:54:52
Mybatis(一)
一JDBC的操作流程1. 创建数据库连接池DataSource2. 通过DataSource获取数据库连接Connection3. 编写要执⾏带?占位符的SQL语句4. 通过Connection及SQL创建操作命令对象Statement5. 替换占位符指定要替换的数据库字段类型占位符索引及要替换的值6. 使⽤Statement执⾏SQL语句7. 查询操作返回结果集ResultSet更新操作返回更新的数量8. 处理结果集9. 释放资源在以上的步骤中124679可以提取出来让mybatic去做二mybatis• MyBatis是⼀款优秀的持久层框架用于简化JDBC的开发• 持久层指的就是持久化操作的层通常指数据访问层(dao)是⽤来操作数据库的简单来说MyBatis是更简单完成程序和数据库交互的框架也就是更简单的操作和读取数据库⼯具2.1 MyBatis入门Mybatis操作数据库的步骤1. 准备工作(创建springboot⼯程、数据库表准备、实体类)2. 引⼊Mybatis的相关依赖配置Mybatis(数据库连接信息)3. 编写SQL语句(注解/XML)4. 测试Mybatis的持久层接⼝规范⼀般都叫XxxMapperMapper注解表⽰是MyBatis中的Mapper接⼝• 程序运⾏时,框架会⾃动⽣成接⼝的实现类对象(代理对象)并给交Spring的IOC容器管理• Select注解代表的就是select查询也就是注解对应⽅法的具体实现内容.测试类上添加了注解SpringBootTest该测试类在运⾏时就会⾃动加载Spring的运⾏环境. 我们通过Autowired这个注解,注⼊我们要测试的类,就可以开始进⾏测试了Select(select * from user_info where username#{username} and password #{password}) ListUserInfo getListByNameAndPassword(String username,String password);在这段代码中如果改成Select(select * from user_info where username#{username1} and password #{password1}) ListUserInfo getListByNameAndPassword(String username,String password);就会报错如下[password, param1, username, param2] 是框架自动帮我们生成的参数其中框架会生成参数名对应的参数和按照参数顺序生成的param1param2........Select(select * from user_info where username#{param1} and password #{param2}) ListUserInfo getListByNameAndPassword(String username,String password);当改成这样的时候运行成功生成的这四个参数password, username是无序的。param1, param2是有序的三MyBatis的基础操作3.1 打印⽇志在Mybatis当中我们可以借助⽇志,查看到sql语句的执⾏、执⾏传递的参数以及执⾏结果在配置⽂件中进⾏配置即可3.2 参数传递需求:查找id4的⽤⼾,对应的SQL就是:select*fromuser_infowhereid4Select(select * from user_info where id 4) UserInfo getUserInfoById();但是这样的话 只能查找id4的数据所以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}) ListUserInfo 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注意:设置 useGeneratedKeystrue 之后,⽅法返回值依然是受影响的⾏数⾃增 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) ListUserInfo 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) ListUserInfo 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) ListUserInfo 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) ListUserInfo getList2();这样的话不仅getList2可以用这个resultsgetList也可以使用这个resultsResultMap(BaseMap) Select(select * from user_info) ListUserInfo getList();使⽤ id 属性给该 Results 定义别名,使⽤ ResultMap 注解来复⽤其他定义的 ResultMap3.6.3 开启驼峰命名(推荐)通常数据库列使用蛇形命名法进行命名(下划线分割各个单词)而Java属性⼀般遵循驼峰命名法约定. 为了在这两种命名方式之间启用自动映射需要将mapUnderscoreToCamelCase 设置为true。四MyBatis XML配置文件Mybatis的开发有两种方式1. 注解2. XML使用Mybatis的注解方式主要是来完成⼀些简单的增删改查功能。如果需要实现复杂的SQL功能建议使用XML来配置映射语句也就是将SQL语句写在XML配置⽂件中MyBatis XML的方式需要以下两步:1. 配置数据库连接字符串和MyBatis2. 写持久层代码XML的配置文件写法1添加依赖mybatis和mysql2配置文件数据库信息xml文件地址3写接口文件4写xml的实现其中resultType为返回结果的类型。需要注意的是如果返回的为集合时resultType设置为集合中元素的类型4.1 配置连接字符串和MyBatis# 数据库连接配置 spring: datasource: url: jdbc:mysql://127.0.0.1:3306/mybatis_test?characterEncodingutf8useSSLfalseallowPublicKeyRetrievaltrue 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/**.xml4.2 写持久层代码持久层代码分两部分1. ⽅法定义Interface2. ⽅法实现:XXX.xmlmapper标签需要指定 namespace 属性表⽰命名空间值为mapper接⼝的全限定名包括全包名.类名select查询标签是⽤来执⾏数据库的查询操作的id 是和 Interface 接⼝中定义的⽅法名称⼀样的表⽰对接⼝的具体实现⽅法。resultType 是返回的数据类型也就是开头我们定义的实体类.4.3 增删改查操作4.3.4 查(Select)同样的,使⽤XML的⽅式进⾏查询,也存在数据封装的问题 我们把SQL语句进⾏简单修改,查询更多的字段内容有的属性会没有被赋值解决办法和注解类似:1. 起别名 2. 结果映射 3. 开启驼峰命名resultMap idBaseMap typecom.luoyaoyun.mybatisdemo.model.UserInfo id columnid propertyid/id result columndelete_flag propertydeleteFlag/result result columncreate_time propertycreateTime/result result columnupdate_time propertyupdateTime/result /resultMap !-- select idgetList resultTypecom.luoyaoyun.mybatisdemo.model.UserInfo-- select idgetList resultMapBaseMap select * from user_info /select五其他查询操作5.1 多表查询多表查询和单表查询类似,只是SQL不同⽽已5.1.1 准备⼯作建⼀张⽂章表,进⾏多表关联查询. ⽂章表的uid,对应⽤⼾表的id5.1.2 数据查询对应ModelData 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 version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.luoyaoyun.mybatisdemo.mapper.ArticleInfoMapper select idgetArticleInfoById resultTypecom.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 /mapperMybatis 不分单表还是多表,主要就是三部分:SQL,映射关系和实体类 通过映射关系,把SQL运⾏结果和实体类关联起来多表查询的缺点1性能低2会产生慢sql所以会进行优化以上述多表查询的例子来看可以在dao层中实现 ta中id1的单表查询得到了uid之后再实现ta中uidtb的id的单表查询5.1.3 一对多需求根据用户ID查询该用户的文章列表SQLSELECT 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,添加articleInfoListNoArgsConstructor 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 ListArticleInfo 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 115.3 排序功能从上⾯的例⼦中,可以得出结论:${}会有SQL注⼊的⻛险,所以我们尽量使⽤#{}完成查询既然如此,是不是${}就没有存在的必要性了呢? 当然不是.接下来看下${}的使⽤场景Select(select * from user_info order by id #{order}) ListUserInfo 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}% ) ListUserInfo 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},%)) ListUserInfo queryAllUserByLike(String key);六. 数据库连接池数据库连接池负责分配、管理和释放数据库连接它允许应⽤程序重复使⽤⼀个现有的数据库连接 ⽽不是再重新建⽴⼀个.没有使⽤数据库连接池的情况每次执⾏SQL语句,要先创建⼀个新的连接对象,然后执⾏SQL语句,SQL 语句执⾏完,再关闭连接对象释放资源.这种重复的创建连接,销毁连接⽐较消耗资源使⽤数据库连接池的情况程序启动时,会在数据库连接池中创建⼀定数量的Connection对象,当客⼾ 请求数据库连接池,会从数据库连接池中获取Connection对象,然后执⾏SQL,SQL语句执⾏完,再把 Connection归还给连接池优点: 1. 减少了⽹络开销 2. 资源重⽤ 3. 提升了系统的性能6.2 使用常⻅的数据库连接池• C3P0• DBCP• Druid• Hikari1. Hikari : SpringBoot默认使⽤的数据库连接池2. Druid如果我们想把默认的数据库连接池切换为Druid数据库连接池,只需要引⼊相关依赖即可dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-3-starter/artifactId version1.2.21/version /dependency如果SpringBoot版本为2.X,使⽤druid-spring-boot-starter依赖dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.1.17/version /dependency