Linux 中的 Systemd
Minio 配置
获取MinIO安装包
下载地址如下:https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230809233022.0.0.x86_64.rpm,通过以下命令可直接将安装包下载至服务器
wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230809233022.0.0.x86_64.rpm
安装MinIO
rpm -ivh minio-20230809233022.0.0.x86_64.rpm
集成Systemd
Systemd概述
Systemd
是一个广泛应用于Linux系统的系统初始化和服务管理器,其可以管理系统中的各种服务和进程,包括启动、停止和重启服务,除此之外,其还可以监测各服务的运行状态,并在服务异常退出时,自动拉起服务,以保证服务的稳定性。系统自带的防火墙服务firewalld
,我们自己安装的mysqld
和redis
均是由Systemd
进行管理的,此处将MinIO服务也交给Systemd管理。编写MinIO服务配置文件
Systemd所管理的服务需要由一个配置文件进行描述,这些配置文件均位于
/etc/systemd/system/
或者/usr/lib/systemd/system/
目录下,下面创建MinIO服务的配置文件。执行以下命令创建并打开
minio.service
文件vim /etc/systemd/system/minio.service
内容如下,具体可参考MinIO官方文档。
[Unit] Description=MinIO Documentation=https://min.io/docs/minio/linux/index.html Wants=network-online.target After=network-online.target AssertFileIsExecutable=/usr/local/bin/minio [Service] WorkingDirectory=/usr/local ProtectProc=invisible EnvironmentFile=-/etc/default/minio ExecStartPre=/bin/bash -c "if [ -z \"${MINIO_VOLUMES}\" ]; then echo \"Variable MINIO_VOLUMES not set in /etc/default/minio\"; exit 1; fi" ExecStart=/usr/local/bin/minio server $MINIO_OPTS $MINIO_VOLUMES Restart=always LimitNOFILE=65536 TasksMax=infinity TimeoutStopSec=infinity SendSIGKILL=no [Install] WantedBy=multi-user.target
注意:
重点关注上述文件中的以下内容即可
EnvironmentFile
,该文件中可配置MinIO服务所需的各项参数ExecStart
,该参数用于配置MinIO服务的启动命令,其中$MINIO_OPTS
、$MINIO_VOLUMES
,均引用于EnvironmentFile
中的变量。MINIO_OPTS
用于配置MinIO服务的启动选项,可省略不配置。MINIO_VOLUMES
用于配置MinIO服务的数据存储路径。
Restart
,表示自动重启
编写
EnvironmentFile
文件执行以下命令创建并打开
/etc/default/minio
文件vim /etc/default/minio
内容如下,具体可参考官方文档。
MINIO_ROOT_USER=minioadmin MINIO_ROOT_PASSWORD=minioadmin MINIO_VOLUMES=/data MINIO_OPTS="--console-address :9001"
注意
MINIO_ROOT_USER
和MINIO_ROOT_PASSWORD
为用于访问MinIO的用户名和密码,密码长度至少8位。MINIO_VOLUMES
用于指定数据存储路径,需确保指定的路径是存在的,可执行以下命令创建该路径。mkdir /data chmod -R 777 /data
MINIO_OPTS
中的console-address
,用于指定管理页面的地址。
启动MinIO
执行以下命令启动MinIO
systemctl start minio
执行以下命令查询运行状态
systemctl status minio
设置MinIO开机自启
systemctl enable minio
访问MinIO管理页面
管理页面的访问地址为:
http://192.168.10.101:9001
注意:
ip
需要根据实际情况做出修改
Minio使用
构建者模式
如何解决Knife4j 侵入性强的问题
Knife4j 的侵入性主要源于对 Swagger 注解的依赖,可通过以下方式优化:
- 减少注解侵入:通过
Docket
配置自动扫描接口、全局参数设置,降低对@Api
、@ApiOperation
等注解的依赖; - 非侵入方案:
- 切换 SpringDoc OpenAPI:支持 OpenAPI 3.0,自动推断接口信息,仅需少量注解(如
@Operation
); - 使用 OpenAPI 规范文件(YAML/JSON):完全解耦代码与文档,无需注解;
- 切换 SpringDoc OpenAPI:支持 OpenAPI 3.0,自动推断接口信息,仅需少量注解(如
- Git 辅助管理:
- 通过分支隔离(如
docs
分支)分离文档与业务代码; - 结合代码审查(PR 流程)规范注解的使用,避免冗余;
- 利用版本历史追溯注解与代码的变更一致性。
- 通过分支隔离(如
- 根本解决:优先采用 SpringDoc 或 OpenAPI 文件,彻底减少注解侵入;若需保留 Knife4j,则通过集中配置、模块化开发和 Git 分支管理降低影响。
Mybatis-plus做了哪些增强
SpringBoot项目开发基本流程
- 搭建Maven工程,确定依赖关系和版本 ,启动版本控制
- 实体类 / Controller / Service / Mapper 导入
- 返回结果类确认
- 单元测试
- 模块业务开发 、 测试
- 模块整合,测试,上线
enums包设计
common模块的常见设计
返回结果设计
public enum ResultCodeEnum {
SUCCESS(200, "成功"),
FAIL(201, "失败"),
PARAM_ERROR(202, "参数不正确"),
SERVICE_ERROR(203, "服务异常"),
DATA_ERROR(204, "数据异常"),
ILLEGAL_REQUEST(205, "非法请求"),
REPEAT_SUBMIT(206, "重复提交"),
DELETE_ERROR(207, "请先删除子集"),
ADMIN_ACCOUNT_EXIST_ERROR(301, "账号已存在"),
ADMIN_CAPTCHA_CODE_ERROR(302, "验证码错误"),
ADMIN_CAPTCHA_CODE_EXPIRED(303, "验证码已过期"),
ADMIN_CAPTCHA_CODE_NOT_FOUND(304, "未输入验证码"),
ADMIN_LOGIN_AUTH(305, "未登陆"),
ADMIN_ACCOUNT_NOT_EXIST_ERROR(306, "账号不存在"),
ADMIN_ACCOUNT_ERROR(307, "用户名或密码错误"),
ADMIN_ACCOUNT_DISABLED_ERROR(308, "该用户已被禁用"),
ADMIN_ACCESS_FORBIDDEN(309, "无访问权限"),
APP_LOGIN_AUTH(501, "未登陆"),
APP_LOGIN_PHONE_EMPTY(502, "手机号码为空"),
APP_LOGIN_CODE_EMPTY(503, "验证码为空"),
APP_SEND_SMS_TOO_OFTEN(504, "验证法发送过于频繁"),
APP_LOGIN_CODE_EXPIRED(505, "验证码已过期"),
APP_LOGIN_CODE_ERROR(506, "验证码错误"),
APP_ACCOUNT_DISABLED_ERROR(507, "该用户已被禁用"),
TOKEN_EXPIRED(601, "token过期"),
TOKEN_INVALID(602, "token非法");
private final Integer code;
private final String message;
ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}
VO 设计
根据返回结果数据设计
@JsonIgnore注解
@JsonIgnore
注解是 Jackson 库中的一个注解,用于在序列化和反序列化 JSON 数据时忽略特定的 Java 类字段。Jackson 是一个广泛使用的 Java 库,用于处理 JSON 数据,特别是在构建 RESTful API 时。
主要用途
排除敏感信息:
- 忽略包含敏感信息的字段,如密码、密钥等。
避免循环引用:
- 在对象之间存在双向关联的情况下,使用
@JsonIgnore
避免无限递归导致的栈溢出。
- 在对象之间存在双向关联的情况下,使用
简化输出:
- 只序列化需要的字段,减少生成的 JSON 数据量。
内部状态管理:
- 忽略仅用于内部计算或状态管理的字段,不暴露给外部用户。
版本控制:
- 在不同版本的 API 中,通过
@JsonIgnore
控制字段的可见性。
- 在不同版本的 API 中,通过
使用场景示例
1. 排除敏感信息
假设有一个 User
类,其中包含用户的敏感信息(如密码),我们希望在序列化时忽略这些字段。
import com.fasterxml.jackson.annotation.JsonIgnore;
public class User {
private String username;
@JsonIgnore
private String password;
private String email;
// Getters and Setters
}
在这种情况下,当 User
对象被序列化为 JSON 时,password
字段将不会出现在最终的 JSON 输出中。
2. 避免循环引用
考虑两个类 Employee
和 Department
,它们之间存在双向关联:
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Employee {
private String name;
private Department department;
// Getters and Setters
}
public class Department {
private String name;
@JsonIgnore
private List<Employee> employees;
// Getters and Setters
}
在这个例子中,@JsonIgnore
注解用于避免在序列化 Department
对象时产生无限递归。如果没有这个注解,序列化 Department
对象会导致 employees
列表中的每个 Employee
对象再次序列化其所属的 Department
,从而引发无限递归。
3. 简化输出
有时你只想序列化对象的一部分字段,以减少传输的数据量。
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Product {
private String id;
private String name;
private double price;
@JsonIgnore
private int stockQuantity;
// Getters and Setters
}
在这个例子中,stockQuantity
字段不会被序列化到 JSON 中,从而简化了输出。
4. 内部状态管理
如果你有一些字段仅用于内部逻辑处理,不需要对外公开,可以使用 @JsonIgnore
注解。
import com.fasterxml.jackson.annotation.JsonIgnore;
public class Order {
private String orderId;
private String customerName;
@JsonIgnore
private transient boolean processed;
// Getters and Setters
}
在这个例子中,processed
字段是一个内部标志,不需要在序列化的 JSON 中出现。
其他相关注解
除了 @JsonIgnore
,Jackson 还提供了其他一些有用的注解来控制 JSON 的序列化和反序列化行为:
@JsonProperty
:指定字段在 JSON 中的名称。public class User { @JsonProperty("user_name") private String username; // Getters and Setters }
@JsonInclude
:控制哪些字段会被包含在序列化结果中。import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class Product { private String id; private String name; private Double price; // null values will be excluded from serialization // Getters and Setters }
@JsonIgnoreProperties
:应用于类级别,忽略指定的属性。import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @JsonIgnoreProperties({"field1", "field2"}) public class Example { private String field1; private String field2; private String field3; // Getters and Setters }
示例代码
以下是完整的示例代码,展示了如何使用 @JsonIgnore
注解来控制 JSON 序列化的行为。
运行结果
运行上述代码将输出以下 JSON 字符串:
Serialized User: {"username":"john_doe","email":"john@example.com"}
Serialized Department: {"name":"Engineering","employees":[{"name":"Alice"},{"name":"Bob"}]}
Serialized Product: {"id":"P001","name":"Laptop","price":999.99}
Serialized Order: {"orderId":"O001","customerName":"Jane Doe"}
总结
@JsonIgnore
注解在处理 JSON 数据时非常有用,特别是在需要排除敏感信息、避免循环引用、简化输出或管理内部状态时。通过合理使用该注解,可以有效控制 JSON 序列化和反序列化的过程,确保数据的安全性和完整性。
当然可以!以下是一个 Markdown 表格,详细说明了 transient
关键字和 @JsonIgnore
注解的区别和适用场景。
transient
vs @JsonIgnore
特性 | transient 关键字 |
@JsonIgnore 注解 |
---|---|---|
作用范围 | 仅影响 Java 的默认序列化机制(java.io.Serializable )。 |
仅影响 Jackson 库的 JSON 序列化和反序列化。 |
标准库支持 | 适用于 Java 标准库中的 ObjectOutputStream 和 ObjectInputStream 。 |
适用于使用 Jackson 进行 JSON 处理的情况。 |
使用示例 | java<br>private transient String password; |
java<br>@JsonIgnore<br>private String password; |
作用域不同 | 影响所有序列化机制,包括 Java 默认序列化。 | 仅影响使用 Jackson 进行的 JSON 序列化和反序列化。 |
适用场景 | 需要将对象持久化到文件、数据库或其他存储介质,并且希望某些字段不被包含在序列化数据中。 | 构建 RESTful API 时,确保敏感信息不会被暴露给客户端。 |
灵活性 | 一旦字段被标记为 transient ,它将永远不会被序列化,无论使用哪种序列化机制。 |
仅影响使用 Jackson 进行的序列化和反序列化,不影响其他序列化机制。 |
组合使用 | 可以在同一字段上同时使用 transient 和 @JsonIgnore ,以确保该字段既不在 Java 默认序列化中出现,也不在 Jackson JSON 序列化中出现。 |
同一字段可以同时被 transient 和 @JsonIgnore 注解修饰,以满足不同的需求。 |
示例代码
以下是完整的示例代码,展示了如何使用 transient
关键字和 @JsonIgnore
注解来控制字段的序列化行为。
运行结果
运行上述代码将输出以下内容:
Serialized UserTransient using transient:
aced000573720018SerializationExample$UserTransient64f4c9d5e3d5b1b30200024c0007emailt0012java/lang/String4c0008usernameq2xpqr001a000ajohn@example.comjohn_doe
Deserialized UserTransient: john_doe, null, john@example.com
Serialized UserJsonIgnore using @JsonIgnore: {"username":"jane_doe","email":"jane@example.com"}
Deserialized UserJsonIgnore: jane_doe, another_secret, jane@example.com
总结
transient
:适用于 Java 默认的序列化机制,用于排除不需要持久化的字段。@JsonIgnore
:适用于使用 Jackson 库进行 JSON 序列化和反序列化,特别是在构建 RESTful API 时,确保敏感信息不会被暴露给客户端。
选择合适的注解取决于你的具体需求和使用的序列化机制。如果你主要处理 JSON 数据,特别是构建 Web 应用程序或 RESTful API,@JsonIgnore
是更好的选择。如果你需要排除字段在所有序列化机制中都不被包含,可以使用 transient
或两者结合使用。
mp 字段自动填充
前后端分离中的数据类型转换
@EnumValue @JsonValue
mp 多表查询
BeanUtils
BeanUtils是Apache Commons组件之一,主要用于简化JavaBean封装数据的操作。它通过简化反射封装参数的步骤,使得对象属性的赋值和获取变得更加便捷,并且支持类型自动转换12。
基本概念和功能
BeanUtils提供了一系列方法来简化JavaBean对象之间的属性复制和操作。其主要功能包括:
属性复制:将一个对象的属性复制到另一个对象中。
属性设置和获取:动态地设置和获取对象的属性值。
类型转换:支持基本类型和包装类型之间的转换3。
使用场景
BeanUtils常用于以下场景:
快速将一个JavaBean各个属性的值赋值给另一个具有相同结构的JavaBean。
快速收集表单中的所有数据到JavaBean中1。
性能和效率问题
BeanUtils内部使用了反射,效率较低,因此在阿里java开发规范中禁止使用。相比之下,Spring的BeanUtils进行了优化,运行效率较高,可以使用。此外,cglib的BeanCopier使用动态技术代替反射,运行效率更高,但在第一次动态生成类时较慢,之后基本接近原始的set操作。
仿自如公寓项目的CRUD思路
后台管理功能:
- 公寓信息管理、房间信息管理、用户管理、租赁管理、标签设置
查找方面分别有:
单表,分表多表查询,嵌套查询
SQL分析
以查询房间信息为例:
第一种长SQL:
<resultMap id="detailMap" type="com.fqxiny.lease.web.admin.vo.room.RoomDetailVo">
<id property="id" column="id" />
<result property="roomNumber" column="room_number" />
<result property="rent" column="rent" />
<result property="apartmentId" column="apartment_id" />
<result property="isRelease" column="is_release" />
<result property="createTime" column="create_time" />
<result property="updateTime" column="update_time" />
<result property="isDeleted" column="is_deleted" />
<association property="apartmentInfo" javaType="com.fqxiny.lease.model.entity.ApartmentInfo">
<id property="id" column="a.id" />
<result property="name" column="a.name" />
<result property="introduction" column="a.introduction" />
<result property="districtId" column="a.district_id" />
<result property="districtName" column="a.district_name" />
<result property="cityId" column="a.city_id" />
<result property="cityName" column="a.city_name" />
<result property="provinceId" column="a.province_id" />
<result property="provinceName" column="a.province_name" />
<result property="addressDetail" column="a.address_detail" />
<result property="latitude" column="a.latitude" />
<result property="longitude" column="a.longitude" />
<result property="phone" column="a.phone" />
<result property="isRelease" column="a.is_release" />
<result property="createTime" column="a.create_time" />
<result property="updateTime" column="a.update_time" />
<result property="isDeleted" column="a.is_deleted" />
</association>
<collection property="graphVoList" ofType="com.fqxiny.lease.model.entity.GraphInfo">
<id property="id" column="g.id" />
<result property="name" column="g.name" />
<result property="itemType" column="g.item_type" />
<result property="itemId" column="g.item_id" />
<result property="url" column="g.url" />
<result property="createTime" column="g.create_time" />
<result property="updateTime" column="g.update_time" />
<result property="isDeleted" column="g.is_deleted" />
</collection>
<collection property="attrValueVoList" ofType="com.fqxiny.lease.web.admin.vo.attr.AttrValueVo">
<id property="id" column="av.id" />
<result property="name" column="av.name" />
<result property="attrKeyId" column="av.attr_key_id" />
<result property="createTime" column="av.create_time" />
<result property="updateTime" column="av.update_time" />
<result property="isDeleted" column="av.is_deleted" />
<result property="attrKeyName" column="ak.attr_key_name"/>
</collection>
<collection property="facilityInfoList" ofType="com.fqxiny.lease.model.entity.FacilityInfo">
</collection>
<collection property="labelInfoList" ofType="com.fqxiny.lease.model.entity.LabelInfo">
</collection>
<collection property="paymentTypeList" ofType="com.fqxiny.lease.model.entity.PaymentType">
</collection>
<collection property="leaseTermList" ofType="com.fqxiny.lease.model.entity.LeaseTerm">
</collection>
</resultMap>
<select id="listAllDetailById" resultMap="detailMap">
</select>
第二种多SQL:
@Override
public RoomDetailVo getDetailById(Long id) {
RoomInfo roomInfo = getById(id);
RoomDetailVo roomDetailVo = new RoomDetailVo();
roomDetailVo.setRoomNumber(roomInfo.getRoomNumber());
roomDetailVo.setRent(roomInfo.getRent());
roomDetailVo.setApartmentId(roomInfo.getApartmentId());
roomDetailVo.setIsRelease(roomInfo.getIsRelease());
roomDetailVo.setApartmentInfo(apartmentInfoService.getById(roomInfo.getApartmentId()));
roomDetailVo.setGraphVoList(graphInfoService.listByRoomId(id));
roomDetailVo.setAttrValueVoList(attrValueService.listByProvidedIds(roomAttrValueService.listByRoomIdLongs(id)));
roomDetailVo.setFacilityInfoList(facilityInfoService.getBaseMapper().selectBatchIds(roomFacilityService.listByRoomId(id)));
roomDetailVo.setLabelInfoList(labelInfoService.getBaseMapper().selectBatchIds(roomLabelService.listByRoomId(id)));
roomDetailVo.setPaymentTypeList(paymentTypeService.getBaseMapper().selectBatchIds(roomPaymentTypeService.listByRoomId(id)));
roomDetailVo.setLeaseTermList(leaseTermService.getBaseMapper().selectBatchIds(roomLeaseTermService.listByRoomId(id)));
return roomDetailVo;
}
方法对比
特征 | 单个复杂的查询 | 多次简单的查询 |
---|---|---|
优点 | - 减少网络开销 - 提高数据库缓存利用率 |
- SQL 简洁易读 - 灵活性高 - 细粒度控制 |
缺点 | - SQL 复杂度高 - 潜在的性能问题(数据量大且没有合适索引) - 锁竞争 |
- 增加网络开销 - 数据库连接数增加 - 事务管理复杂 |
适用场景 | - 中等规模数据 - 对性能有一定要求 |
- 大规模数据 - 各个表之间关联不强 |
索引建议 | - room_info(id, apartment_id) - apartment_info(id) - graph_info(item_id, item_type) - attr_value(room_id, attr_key_id) - attr_key(id) |
不需要特别针对单个查询优化索引,但可以为每个子查询创建合适的索引 |
SQL优化思路
优化方向 | 具体方法 | 示例/说明 |
---|---|---|
索引优化 | 1. 使用覆盖索引避免回表 | SELECT id, name FROM user WHERE id = 1 (索引包含 id 和 name ,无需回表) |
2. 联合索引遵循最左匹配原则 | 索引 (a,b,c) ,查询 WHERE a=1 AND b=2 可命中,但 WHERE b=2 无法命中前导列 |
|
3. 避免对无索引字段排序 | ORDER BY name 需确保 name 有索引,否则全表扫描排序 |
|
查询语句优化 | 1. 避免 SELECT * |
明确指定字段,如 SELECT id, name ,减少数据传输和IO |
2. 避免字段函数计算 | WHERE YEAR(create_time) = 2023 → 转为 WHERE create_time BETWEEN ... |
|
3. 避免 %LIKE 前导通配符 |
LIKE '%abc' → 全表扫描,建议改用前缀匹配如 'abc%' |
|
连接优化 | 1. 连表字段字符集一致 | 确保 JOIN 字段的字符集和排序规则一致,避免隐式转换导致全表扫描 |
执行计划分析 | 利用 EXPLAIN 分析执行计划 |
通过 EXPLAIN SELECT ... 查看索引使用、扫描类型、行数等性能瓶颈 |
其他优化 | 1. 利用缓存(如 Redis) | 缓存高频查询结果(如热点数据),减少数据库压力 |
2. 业务逻辑优化 | - 分页避免大 offset (如 LIMIT 100000, 10 )- 批量操作(如 INSERT 、UPDATE )减少单次查询次数 |
关键记忆点
索引是核心:
- 覆盖索引(避免回表)→ 字段在索引中
- 联合索引 → 最左匹配,否则无法命中
- 排序字段 → 必须有索引,否则全表扫描。
查询语句细节:
SELECT *
→ 字段精简- 函数计算 → 避免字段上使用
%LIKE
→ 前导通配符慎用。
连接与字符集:
JOIN
字段字符集一致 → 避免隐式转换。
工具与业务:
EXPLAIN
→ 分析执行计划- 缓存 → 高频查询结果
- 分页/批量 → 减少单次查询压力。
一句话总结
“索引是王道,查询要精准,避免函数与通配符,连接字段要一致,缓存分页巧优化!”
以下是根据你提供的总结分类提炼的 MySQL索引相关要点表格,整理清晰且便于记忆:
MySQL索引相关要点总结表
分类 | 要点 | 说明/示例 |
---|---|---|
索引有效性问题 | 1. 查询条件不匹配索引列 | 索引列未出现在WHERE 、JOIN 等条件中,无法命中索引 |
2. 低基数列索引效果差 | 如性别 字段(男/女),区分度低,索引收益有限 |
|
3. 小表可能选择全表扫描 | 全表扫描的IO成本低于索引访问,MySQL可能选择全表扫描 | |
4. 统计信息不准确导致误判 | 表数据分布变化未更新统计信息,优化器选择次优计划 | |
索引建立注意事项 | 1. 避免盲目建索引 | 需结合查询条件和业务场景,避免冗余索引 |
2. 避免在重复值高的字段建索引 | 如性别 字段,但若需频繁查询特定值(如男 ),仍可建索引 |
|
3. 长字段慎建索引 | 如长文本字段(如VARCHAR(255) ),索引存储空间大,效率低 |
|
4. 高频修改的表减少索引 | 增删改操作多时,过多索引会增加写入开销 | |
5. 联合索引优化多条件查询 | 如WHERE a=1 AND b=2 ,建立(a,b) 联合索引,避免多个单列索引 |
|
6. 对排序/分组字段建索引 | ORDER BY name 时,需确保name 有索引,避免全表扫描后排序 |
|
索引数量是否越多越好 | 1. 时间成本:增删改操作开销增加 | 每次数据变更需更新所有相关索引,索引越多,时间越长 |
2. 优化器选择时间增加 | 索引过多时,优化器需要更多时间评估最优执行计划 | |
3. 空间成本:存储B+树占用额外空间 | 每个索引需要额外存储,数据量大时空间消耗显著 | |
4. 不是越多越好,需合理选择 | 根据查询模式和业务需求,权衡读写性能与资源消耗 |
关键记忆点
索引有效性的核心:
- 索引能否命中取决于查询条件、字段基数、表大小及统计信息准确性。
- EXPLAIN 是排查索引是否生效的利器,重点关注
type
(是否为index/range
)、key
(是否命中索引)、rows
(估算扫描行数)。
建索引的黄金法则:
- 避免冗余:仅对高频查询条件、排序/分组字段建索引。
- 联合索引:多条件查询用联合索引(遵循最左匹配原则)。
- 慎用长字段和低基数字段:避免浪费资源。
索引数量的平衡:
- 读写权衡:索引越多,读快写慢。
- 合理规划:根据业务场景选择,而非盲目追求“全索引”。
示例场景
- 无效索引示例:
表user
有索引(id, name)
,但查询SELECT * FROM user WHERE age=20
,因age
未在索引列中,索引无效。 - 有效索引示例:
联合索引(status, create_time)
,查询WHERE status='active' AND create_time BETWEEN ...
可命中索引,减少回表。
通过以上表格和要点,可以快速掌握MySQL索引的优化原则和排查方法。
BeanUtils Apache对比Spring
对比项 | Apache Commons BeanUtils | Spring BeanUtils |
---|---|---|
核心类 | org.apache.commons.beanutils.BeanUtils |
org.springframework.beans.BeanUtils |
功能 | 基础的 Bean 属性操作: - 属性复制( copyProperties )- 属性描述( describe )- 动态访问属性( getProperty/setProperty ) |
针对 Spring 生态的 Bean 操作: - 属性复制( copyProperties )- 转换( beanToMap )- 深度克隆(需配合 BeanWrapper ) |
依赖 | 需引入 commons-beanutils 依赖(独立于 Spring) |
需引入 Spring 核心依赖(如 spring-beans ) |
类型转换 | 使用 ConvertUtils 进行类型转换,需手动注册转换器(如 register ) |
内置 Spring 的类型转换机制(如 ConversionService 或 PropertyEditor ) |
线程安全 | 部分方法(如 BeanUtils 的静态方法)可能不线程安全,需注意缓存问题 |
默认线程安全,与 Spring 的 BeanWrapper 集成更可靠 |
适用场景 | 独立于 Spring 的项目,需基础的 Bean 属性操作(如属性复制、动态访问) | Spring 生态系统内,需与 Spring 管理的 Bean(如依赖注入后的 Bean)交互 |
异常处理 | 抛出 IllegalAccessException 、InvocationTargetException 等反射异常 |
抛出 IllegalArgumentException 、BeanInstantiationException 等 Spring 特定异常 |
额外功能 | 支持通过 PropertyDescriptor 描述 Bean 属性,提供更底层的反射操作 |
支持 @PostConstruct 等注解的处理,与 Spring 的生命周期管理集成 |
性能 | 纯反射实现,性能较低(尤其在频繁调用时) | 通过 BeanWrapper 缓存反射信息,性能更高 |
维护状态 | Apache Commons 项目活跃,但更新较慢(Apache 项目通用节奏) | Spring 持续维护,更新频繁,与 Spring 版本强绑定 |
推荐使用情况 | 非 Spring 项目,或需轻量级的 Bean 操作(如属性复制、动态访问) | Spring Boot/Spring MVC 项目,需与 Spring 管理的 Bean 集成 |
关键差异总结
依赖与集成:
- Apache Commons:独立于 Spring,适合非 Spring 项目。
- Spring:依赖 Spring 框架,适合 Spring 生态系统。
性能:
- Spring 的
BeanUtils
通过BeanWrapper
缓存反射信息,性能更优。 - Apache Commons 纯反射实现,频繁调用时性能较差。
- Spring 的
类型转换:
- Spring 内置更强大的类型转换机制(如
ConversionService
),支持复杂类型转换。 - Apache Commons 需手动注册转换器,灵活性较低。
- Spring 内置更强大的类型转换机制(如
线程安全:
- Spring 的
BeanUtils
更安全,与BeanWrapper
集成后避免缓存问题。 - Apache Commons 静态方法可能因缓存导致线程不安全。
- Spring 的
扩展性:
- Spring 支持与 Spring 的生命周期管理(如
@PostConstruct
)、依赖注入等深度集成。 - Apache Commons 提供基础反射操作,但功能较单一。
- Spring 支持与 Spring 的生命周期管理(如
对比
Apache Commons BeanUtils
// 属性复制
BeanUtils.copyProperties(sourceBean, targetBean);
// 动态获取属性值
Object value = BeanUtils.getProperty(bean, "fieldName");
Spring BeanUtils
// 属性复制(忽略空值)
BeanUtils.copyProperties(sourceBean, targetBean, "ignoredField");
// 转换 Bean 到 Map
Map<String, Object> map = BeanUtils.beanToMap(bean);
选择建议
- 选 Apache Commons:当项目不依赖 Spring,且仅需基础的 Bean 属性操作(如属性复制、动态访问)。
- 选 Spring:当项目基于 Spring 生态,需与 Spring 管理的 Bean 集成,或需要更强大的类型转换和性能优化。
@One @Many
@One
注解总结
参数 | 说明 | 示例 |
---|---|---|
select |
关联查询的 Mapper 方法全限定名(或接口方法名)。 | select = "com.example.mapper.UserMapper.selectAddressByUserId" |
column |
主表字段名,用于传递给关联查询的参数(单值)。 | column = "user_id" |
parentColumn |
主表的多个字段名(用逗号分隔),用于传递多个参数给关联查询。 | parentColumn = "user_id, region_id" |
fetchType |
指定关联查询的加载方式(EAGER 或 LAZY )。 |
fetchType = FetchType.EAGER |
作用 | 一对一关联查询,用于在结果中嵌套单个关联对象。 | |
使用场景 | 需要查询主表记录的单个关联对象(如用户和其地址)。 |
@Results({
@Result(id = true, column = "user_id", property = "id"),
@Result(property = "address",
column = "user_id",
javaType = Address.class,
one = @One(select = "com.example.mapper.AddressMapper.selectByUserId"))
})
User selectUserWithAddress(@Param("user_id") int userId);
@Many
注解总结
参数 | 说明 | 示例 |
---|---|---|
select |
关联查询的 Mapper 方法全限定名(或接口方法名)。 | select = "com.example.mapper.OrderMapper.selectOrdersByUserId" |
column |
主表字段名,用于传递给关联查询的参数(单值)。 | column = "user_id" |
parentColumn |
主表的多个字段名(用逗号分隔),用于传递多个参数给关联查询。 | parentColumn = "user_id, region_id" |
fetchType |
指定关联查询的加载方式(EAGER 或 LAZY )。 |
fetchType = FetchType.EAGER |
作用 | 一对多关联查询,用于在结果中嵌套多个关联对象(如用户和其订单)。 | |
使用场景 | 需要查询主表记录的多个关联对象(如用户和其订单列表)。 |
@Results({
@Result(id = true, column = "user_id", property = "id"),
@Result(property = "orders",
column = "user_id",
javaType = ArrayList.class,
many = @Many(select = "com.example.mapper.OrderMapper.selectOrdersByUserId"))
})
User selectUserWithOrders(@Param("user_id") int userId);
对比总结
特性 | @One |
@Many |
---|---|---|
关联类型 | 一对一(单个对象) | 一对多(集合对象) |
参数传递 | 通过单个或多个主表字段传递参数 | 同上 |
返回类型 | 单个对象(如 Address ) |
集合对象(如 List<Order> ) |
适用场景 | 用户-地址、订单-支付信息等 | 用户-订单、分类-商品等 |
注意事项
- 必须与
@Results
结合使用:@One
和@Many
需要嵌套在@Result
中,并通过@Results
注解定义在 Mapper 方法上。 - 参数传递:
column
:直接使用主表单个字段名。parentColumn
:使用多个字段名(如parentColumn = "user_id, region_id"
)。
- 延迟加载(Lazy Load):
- 需要配置 MyBatis 的延迟加载支持(如
configuration.setLazyLoadingEnabled(true)
)。
- 需要配置 MyBatis 的延迟加载支持(如