一,Mybatis介绍
MyBatis 是一款优秀的持久层(orm)框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
- MyBatis本是Apache的一个开源项目iBatis,2010年这个项目由apache迁移到了google code,并且改名为MyBatis。2013 年11月迁移到Github。
- 官网: mybatis – MyBatis 3 | 简介
二,Mybatis基础操作
2.1 Mybatis快速入门
首先,需要在Maven中导入Mybatis的坐标
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
以Mybatis操作MySQL为例,导入Java操作Mysql的驱动:
<!--mysql8.0之前的版本-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.45</version>
</dependency>
<!--MySQL8.0及之后的版本-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
配置MySQL的连接信息,在项目application.properties或其他配置文件中配置:
spring:
#mysql数据库配置
datasource:
#连接MYSQL的账号
username: root
#连接MYSQL的密码
password: 123456
#连接MYSQL的地址
url: jdbc:mysql://172.31.154.122:3306/demo
#mysql驱动的全类名,这里是8.0以上的
#8.0之前的是:com.mysql.jdbc.Driver
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
#开启类别命名自动扫描
typeAliasesPackage: com.normaling.**.domain
#开启mapper扫描路径
mapperLocations: classpath*:mapper/**/*Mapper.xml
configuration:
#指定mybatis日志输出的位置,这里是输出到控制台
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#开启小驼峰命名转换。将column=a_column--->aColumn
map-underscore-to-camel-case: true
创建对应数据库表的实体类,用于接受从数据库中查询的数据。
在Mapper层的类中使用注解的方式来编写SQL语句
@Mapper是Mybatis提供的注解,用于将我们的java类较给IOC容器管理。
@Select就是查询语句了,对应的还有@Insert,@Delete,@Update
Mybatis编写SQL语句分为:
- 静态SQL(用注解直接写SQL)
- 动态SQL(编写xml文件)(主要通过xml编写SQL)
2.2 数据库连接池
数据库连接池:
- 数据库连接池是个容器,负责分配、管理数据库连接(Connection)
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个
- 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
数据库连接池标准接口:DataSource
- 官方(sun)提供的数据库连接池接口,由第三方组织实现此接口
- 功能:获取连接:
常见的数据库连接池:
Druid (德鲁伊)
- Druid连接池是阿里巴巴开源的数据库连接池项目
- 功能强大,性能优秀,是ava语言最好的数据库连接池之一
切换Druid数据库连接池的方法:
官方地址: github.com/alibaba/dru…
-
引入Druid
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.16</version> </dependency> -
修改数据源配置
spring: datasource: # 基本数据库连接信息 url: jdbc:mysql://localhost:3306/your_db?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource # 指定使用 Druid 连接池 # Druid 连接池专属配置 druid: # --- 连接池核心参数 --- initial-size: 5 # 初始化时建立的物理连接数(建议根据并发量调整) min-idle: 5 # 最小空闲连接数(长期维持的闲置连接数) max-active: 20 # 最大活跃连接数(高并发时可适当调高,避免等待) max-wait: 60000 # 获取连接时的最大等待时间(毫秒),超时则抛异常 time-between-eviction-runs-millis: 60000 # 检查空闲连接的间隔时间(毫秒) min-evictable-idle-time-millis: 300000 # 连接保持空闲的最小时间,超时则被关闭 validation-query: SELECT 1 # 用于检测连接有效性的 SQL(如 MySQL 用 SELECT 1) test-while-idle: true # 空闲时是否检测连接有效性(建议开启) test-on-borrow: false # 获取连接时是否检测有效性(影响性能,不建议开启) test-on-return: false # 归还连接时是否检测有效性(影响性能,不建议开启) # --- 监控统计配置 --- stat-view-servlet: enabled: true # 启用内置的监控页面 url-pattern: /druid/* # 监控页面的访问路径 login-username: admin # 监控页面登录用户名(生产环境建议修改) login-password: admin # 监控页面登录密码(生产环境必须修改) reset-enable: false # 禁用监控页面的 "Reset All" 功能(安全考虑) web-stat-filter: enabled: true # 启用 Web 应用监控 url-pattern: /* # 监控所有 URL 请求 exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 排除静态资源 session-stat-enable: true # 统计 Session 使用情况 principal-session-name: user # 用户信息的 Session Key(用于监控用户行为) # --- 高级功能(可选)--- filters: stat,wall,slf4j # 启用统计、防火墙、日志过滤器(需添加依赖) filter: stat: log-slow-sql: true # 记录慢查询 slow-sql-millis: 2000 # 慢查询阈值(单位:毫秒) wall: config: multi-statement-allow: true # 允许多条 SQL 语句(根据需求开启)
三,Mybatis静态SQL
3.1 参数占位符
参数顺序和注解里面的参数顺序需要保持一致。
3.2 删除
默认返回操作影响的记录数,用int存储。
举例:根据主键id删除数据。
#{id}所包含的内容在mybatis中会先变成?占位符,这个叫预编译SQL
预编译SQL和普通SQL语句的执行对比:
注:SQL注入是通过操作输入的数据来修改事先定义好的SQL语句,以达到执行代码对服务器进行攻击的方法,Mybatis能防止SQL注入是因为这个预编译SQL,因为不管传入怎么样的字符串,他都会将其当作一个参数传入!
3.3 新增
举例:新增人员信息
其中参数占位符里面的是Emp类里面的属性名,一般采用小驼峰命名方法,而在数据库表中一般采用小写使用下划线分割的方式来命名。
举例:新增(主键返回)
即insert插入数据后,将对应记录里面的主键封装到你传入的参数Emp emp里面。这个就叫新增主键返回。
3.4 更新
默认返回操作影响的记录数,用int存储。
3.5 查询
根据主键id查询:
但是在查询回显数据的时候需要注意数据封装问题:
- 实体类属性名和数据库表查询返回的字段名一致,mybatis会自动封装
- 如果实体类属性名 和 数据库表查询返回的字段名不一致,不能自动封装
- 假设你查询返回的是一张表,里面每一条记录数代表的就是一个Emp信息,那么返回值可以用List<Emp>来存储整张表。
解决方法:
-
给数据库表中字段名和对应java属性名不一致的地方在select语句中起别名。
-
通过**@Results,@Result**注解手动映射数据库字段和java类字段
column里面是数据库表字段名,property里面是java类字段名。
-
在mybatis中开启小驼峰转换,让他自动映射。
a_column ==》 aColumn这种
mybatis: configuration: #指定mybatis日志输出的位置,这里是输出到控制台 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #开启小驼峰命名转换。将column=a_column--->aColumn map-underscore-to-camel-case: true #开启mapper扫描路径 mapper-locations: classpath:mapper/*.xml
条件查询
注意:
四,xml映射
- 随着用户的输入或外部条件的变化而变化的SQL语句,我们称为 动态SQL。
- xml映射SQL主要是用来配置复杂的,动态的SQL语句。
- resultType是单条记录封装的返回值的全类名。
一般我们会通过在配置文件中指定扫描mapper.xml路径
mybatis: # 搜索指定包别名 typeAliasesPackage: com.gtzc.**.domain # 配置mapper的扫描,找到所有的mapper.xml映射文件 mapperLocations: classpath*:mapper/**/*Mapper.xml # 加载全局的配置文件 configLocation: classpath:mybatis/mybatis-config.xml效果如图:
![]()
4.1 SQL语句标签
-
select标签
<select id="selectById" resultMap="BaseResultMap" parameterType="Object"> select * from user where id=#{id} </select> -
update标签
<update id="update" parameterType="Object"> update user set <if test="name != null"> NAME = #{name},</if> <if test="sex != null"> SEX = #{sex} </if> where ID = #{id} </update> -
delete标签
<delete id="deleteById" parameterType="Object"> delete from user where id=#{id} </delete> -
insert标签
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into user name=#{name} </insert>useGeneratedKeys和keyProperty是insert标签独有的属性
useGeneratedKeys=true:告诉 MyBatis 使用 JDBC 的getGeneratedKeys方法来获取由数据库自动生成的主键值。keyProperty="id":指定属性名,这个属性将会在插入操作完成后被赋值为数据库生成的主键值。在这个例子中,id是DemoUser对象的一个属性。
标签内属性说明:
- parameterType:parameterType: 传入给语句的参数类型。
- id:唯一标识需要和mapper文件里面的方法名保持一致。
4.2 接口方法传入多个参数解决方法
在上述标签中,我们的接口方法都只传入了一个参数,然后用parameterType来指定我们的值,但当我们的接口方法出现多个参数时解决方法:
4.2.1 使用Map
接口:
public interface UserMapper {
User selectUser(@Param("id") int id, @Param("name") String name);
}
xml配置:
<select id="selectUser" parameterType="map" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id} AND name = #{name}
</select>
4.2.2 使用自定义对象
就是将多个参数新建一个对象来封装起来。
4.2.3 使用 @Param 注解
如果使用 MyBatis 3.4.0 及以上版本,可以直接在方法参数上使用 @Param 注解
public interface UserMapper {
User selectUser(@Param("id") int id, @Param("name") String name);
}
<select id="selectUser" resultType="com.example.User">
SELECT * FROM users WHERE id = #{id} AND name = #{name}
</select>
4.3 if标签
用于判断条件是否成立。使用test属性进行条件判断,如果条件为true,则拼接SQL。
如下图所示:
4.4 choose标签
<choose> 标签类似于 Java 中的 switch 语句,用于在多个条件中选择一个执行分支。它可以包含多个 <when> 和一个 <otherwise> 标签,每个 <when> 标签表示一个条件分支,而 <otherwise> 标签表示默认分支。
<select id="selectUser" resultType="User">
SELECT *
FROM user
<where>
<choose>
<when test="name != null">
AND name = #{name}
</when>
<when test="age != null">
AND age = #{age}
</when>
<otherwise>
AND id = #{id}
</otherwise>
</choose>
</where>
</select>
4.5 where标签
上述案例中,存在bug,当第一个条件name为null时会SQL会拼接成错误的SQL:
select * from emp where and gender = 1 order by update_time desc;
会多出一个and导致SQL语法错误,解决方法就是加上where标签,如下图:
where标签有两个作用:
- 如果标签内的if条件都不成立他就不会生成where
- 如果标签内存在多余的and或者or会自动去除掉他.
4.6 Set标签
Set标签和where标签的作用都是为了防止因为if标签导致错误的SQL拼接。
set标签会去除多余的逗号即
update emp set id=1,name='zhangsan';
假设这个是动态SQL生成的,那么在if判断时可能会产生多余的逗号,这个时候就使用set标签包裹即可。
4.7 trim标签
trim 一般用于去除 SQL 语句中多余的 AND 关键字、逗号,或者给 SQL 语句前拼接 where、set 等后缀,可用于选择性插入、更新、删除或者条件查询等操作。trim 语法格式如下。
<trim prefix="前缀" suffix="后缀" prefixOverrides="忽略前缀字符" suffixOverrides="忽略后缀字符">
SQL语句
</trim>
属性解释:
-
prefix:给sql语句拼接的前缀
-
suffix:给sql语句拼接的后缀
-
**prefixOverrides:**去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定
假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND"
-
suffixOverrides:去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
trim标签常见用法:
<insert id="insertDsMatirailPurchaseRegister" parameterType="DsMatirailPurchaseRegister">
insert into ds_matirail_purchase_register
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="purchaseId != null">purchase_id,</if>
<if test="purchaseObjects != null">purchase_objects,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="purchaseId != null">#{purchaseId},</if>
<if test="purchaseObjects != null">#{purchaseObjects},</if>
</trim>
</insert>
4.8 foreach标签
foreach用来遍历集合,用法如下图:
- collection: 遍历的集合
- item: 遍历出来的元素
- separator: 分隔行
- open: 遍历开始前拼接的SQL片段
- close: 遍历结束后拼接的SQL片段
forEach常见的用法:
-
批量删除
@Mapper public interface DemoDao { int deleteByIds(@Param("ids") List<Integer> ids); }<delete id="deleteByIds"> DELETE FROM DemoUser WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </delete> -
批量插入
@Mapper public interface DemoDao { int insertUsers(@Param("users") List<DemoUser> users); }<insert id="insertUsers"> INSERT INTO DemoUser (name, age) VALUES <foreach collection="users" item="user" separator=","> (#{user.name}, #{user.age}) </foreach> </insert> -
批量查询
@Mapper public interface DemoDao { List<DemoUser> selectByIds(@Param("ids") List<Integer> ids); }<select id="selectByIds" resultType="com.example.DemoUser"> SELECT id, name, age FROM DemoUser WHERE id IN <foreach collection="ids" item="id" open="(" separator="," close=")"> #{id} </foreach> </select>
4.9 SQL和include标签
sql标签和include标签是搭配使用的,sql标签用来将重复内容的sql抽取出来,include负责调用这个sql语句。
4.10 resultMap标签
4.10.1 resultMap标签基础使用
作用:将SQL语句的查询返回结果和java对象绑定。
<resultMap type="MmDevice" id="MmDeviceResult">
<result property="deviceId" column="device_id" />
<result property="groupId" column="group_id" />
<result property="orderId" column="order_id" />
<result property="goodsId" column="goods_id" />
<result property="goodsStyle" column="goods_style" />
<result property="updateTime" column="update_time" />
<result property="status" column="status" />
<result property="operateTime" column="operate_time" />
</resultMap>
-
type对应的java类的类名
-
id是给select语句等需要返回对象的resultMap属性使用的。
例如此时可以给select标签使用:
<select id="selectMmDeviceByDeviceId" parameterType="Long" resultMap="MmDeviceResult">
select device_id, group_id, order_id, goods_id, goods_style, update_time, status, operate_time from mm_device where device_id = #{deviceId}
</select>
此时resultMap对应的就是resultMap的id。
属性解释:
-
property对应的是java类里面的属性名
-
column对应的是数据库里面字段名
需要注意,resultType和resultMap不能同时使用!
4.10.1 resultMap的继承、复用、嵌套
**继承:**继承已存在的 resultMap 标签进行扩展
举例:
-
先有如下文件
public class User { private int id; private String username; private String firstName; // Getter 和 Setter 略 } -
xml的写法
<!-- 基础的 resultMap 定义 --> <resultMap id="BaseUserResultMap" type="User"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="email" column="email" /> </resultMap> <!-- 继承 BaseUserResultMap 的 resultMap,并增加一个额外的字段映射 --> <resultMap id="ExtendedUserResultMap" type="User" extends="BaseUserResultMap"> <result property="firstName" column="first_name" /> </resultMap>
**复用:**跨mapper文件引用现存的 resultMap 标签
假设我们有两个不同的 XML 文件,分别定义了不同的 resultMap,我们想要在一个文件中引用另一个文件中定义的 resultMap。
-
File1
<!-- File1.xml 中定义的 resultMap --> <resultMap id="BaseUserResultMap" type="User"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="email" column="email" /> </resultMap> -
File2
<!-- File2.xml 中引用 File1.xml 中的 resultMap --> <resultMap id="ExtendedUserResultMap" type="User"> <!--通过这个resultMap refild来实现复用--> <resultMap refid="BaseUserResultMap" /> <result property="firstName" column="first_name" /> <result property="lastName" column="last_name" /> </resultMap>
也可以跨文件复用sql标签的内容
<!-- File1.xml 中定义的 SQL 片段 --> <sql id="selectFields"> id, username, email </sql> <!-- File2.xml 中引用 File1.xml 中的 SQL 片段 --> <select id="selectUsers" resultMap="ExtendedUserResultMap"> SELECT <include refid="selectFields"/> FROM users </select>
**嵌套:**指在一个 resultMap 中嵌套另一个 resultMap 或 <collection> 元素来处理数据库查询结果中的复杂对象关系。这种技术使得可以将多个数据库表中的数据映射到一个复杂的 Java 对象中,或者处理一对多关系的数据
-
嵌套单个对象
public class User{ private int id; private String username; private String email; private UserDetails details; } private class UserDetails{ private int id; private String firstName; private String lastName; private String address; }<!-- 定义 user 表的 resultMap --> <resultMap id="UserResultMap" type="User"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="email" column="email" /> <!-- 嵌套 user_details 表的 resultMap --> <!--property对应的是java类中属性名,javaType对应的是java类中类名,resultMap对应下放的id--> <association property="details" javaType="UserDetails" resultMap="UserDetailsResultMap" /> </resultMap> <!-- 定义 user_details 表的 resultMap --> <resultMap id="UserDetailsResultMap" type="UserDetails"> <id property="id" column="id" /> <result property="firstName" column="first_name" /> <result property="lastName" column="last_name" /> <result property="address" column="address" /> </resultMap> -
嵌套集合对象
public class User{ private int id; private String username; private String email; //就是嵌套这个 private List<Order> orders; } private class Order{ private int id; private String orderNumber; private String orderDate; }<!-- 定义 user 表的 resultMap,嵌套 order 表的 collection --> <resultMap id="UserOrdersResultMap" type="User"> <id property="id" column="id" /> <result property="username" column="username" /> <result property="email" column="email" /> <!-- 嵌套 order 表的 collection --> <!--property对应的是java类中属性名,ofType对应的是java类中类名,resultMap对应下放的id--> <collection property="orders" ofType="Order" resultMap="OrderResultMap" /> </resultMap> <!-- 定义 order 表的 resultMap --> <resultMap id="OrderResultMap" type="Order"> <id property="id" column="id" /> <result property="orderNumber" column="order_number" /> <result property="orderDate" column="order_date" /> </resultMap>
嵌套+继承复合写法:一般用于主子表的关系映射
public class User{
private int id;
private String username;
private String email;
//就是嵌套这个
private List<Order> orders;
}
private class Order{
private int id;
private String orderNumber;
private String orderDate;
}
<!--主表映射-->
<resultMap type="User" id="UserResult">
<result property="id" column="id" />
<result property="username" column="username" />
<result property="email" column="email" />
</resultMap>
<!--子表映射-->
<resultMap type="Order" id="OrderResult">
<result property="id" column="sub_id" />
<result property="orderNumber" column="sub_order_number" />
<result property="orderDate" column="sub_order_date" />
</resultMap>
<!--type是主表的type,继承的值也是主表的resultMap的id,主子表查询的resultMap就采用这个的id-->
<resultMap id="OrderExtendData" type="User" extends="UserResult">
<!-- resultMap对应的是子表的resultMap的id-->
<!-- notNullColumn代表不能为null的字段-->
<!-- javaType代表的是List集合-->
<!-- property对应的是子表在主表里面的属性名 -->
<collection property="orders"
notNullColumn="sub_id" javaType="java.util.List"
resultMap="OrderResult" />
</resultMap>
五,PageHelper分页查询
分页查询逻辑:
SQL语句逻辑:
需要在后端专门创建一个PageBean来存储分页查询的数据:
后端执行流程:
使用PageHelper进行分页查询与普通分页查询的对比:
使用PageHelper需要引入依赖: