每日学习Java之一万个为什么

发布于:2025-03-29 ⋅ 阅读:(33) ⋅ 点赞:(0)

Linux 中的 Systemd

Minio 配置

  1. 获取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
    
  2. 安装MinIO

    rpm -ivh minio-20230809233022.0.0.x86_64.rpm
    
  3. 集成Systemd

    • Systemd概述

      Systemd是一个广泛应用于Linux系统的系统初始化和服务管理器,其可以管理系统中的各种服务和进程,包括启动、停止和重启服务,除此之外,其还可以监测各服务的运行状态,并在服务异常退出时,自动拉起服务,以保证服务的稳定性。系统自带的防火墙服务firewalld,我们自己安装的mysqldredis均是由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_USERMINIO_ROOT_PASSWORD为用于访问MinIO的用户名和密码,密码长度至少8位

      • MINIO_VOLUMES用于指定数据存储路径,需确保指定的路径是存在的,可执行以下命令创建该路径。

        mkdir /data
        chmod -R 777 /data
        
      • MINIO_OPTS中的console-address,用于指定管理页面的地址。

  4. 启动MinIO

    执行以下命令启动MinIO

    systemctl start minio
    

    执行以下命令查询运行状态

    systemctl status minio
    

    设置MinIO开机自启

    systemctl enable minio
    
  5. 访问MinIO管理页面

    管理页面的访问地址为:http://192.168.10.101:9001

    注意

    ip需要根据实际情况做出修改

Minio使用

构建者模式

如何解决Knife4j 侵入性强的问题

Knife4j 的侵入性主要源于对 Swagger 注解的依赖,可通过以下方式优化:

  1. 减少注解侵入:通过 Docket 配置自动扫描接口、全局参数设置,降低对 @Api@ApiOperation 等注解的依赖;
  2. 非侵入方案
    • 切换 SpringDoc OpenAPI:支持 OpenAPI 3.0,自动推断接口信息,仅需少量注解(如 @Operation);
    • 使用 OpenAPI 规范文件(YAML/JSON):完全解耦代码与文档,无需注解;
  3. Git 辅助管理
    • 通过分支隔离(如 docs 分支)分离文档与业务代码;
    • 结合代码审查(PR 流程)规范注解的使用,避免冗余;
    • 利用版本历史追溯注解与代码的变更一致性。
  4. 根本解决:优先采用 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 时。

主要用途

  1. 排除敏感信息

    • 忽略包含敏感信息的字段,如密码、密钥等。
  2. 避免循环引用

    • 在对象之间存在双向关联的情况下,使用 @JsonIgnore 避免无限递归导致的栈溢出。
  3. 简化输出

    • 只序列化需要的字段,减少生成的 JSON 数据量。
  4. 内部状态管理

    • 忽略仅用于内部计算或状态管理的字段,不暴露给外部用户。
  5. 版本控制

    • 在不同版本的 API 中,通过 @JsonIgnore 控制字段的可见性。

使用场景示例

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. 避免循环引用

考虑两个类 EmployeeDepartment,它们之间存在双向关联:

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 标准库中的 ObjectOutputStreamObjectInputStream 适用于使用 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


优化方向 具体方法 示例/说明
索引优化 1. 使用覆盖索引避免回表 SELECT id, name FROM user WHERE id = 1(索引包含 idname,无需回表)
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
- 批量操作(如 INSERTUPDATE)减少单次查询次数

关键记忆点

  1. 索引是核心

    • 覆盖索引(避免回表)→ 字段在索引中
    • 联合索引 → 最左匹配,否则无法命中
    • 排序字段 → 必须有索引,否则全表扫描。
  2. 查询语句细节

    • SELECT *字段精简
    • 函数计算 → 避免字段上使用
    • %LIKE前导通配符慎用
  3. 连接与字符集

    • JOIN 字段字符集一致 → 避免隐式转换
  4. 工具与业务

    • EXPLAIN分析执行计划
    • 缓存 → 高频查询结果
    • 分页/批量 → 减少单次查询压力

一句话总结

“索引是王道,查询要精准,避免函数与通配符,连接字段要一致,缓存分页巧优化!”

以下是根据你提供的总结分类提炼的 MySQL索引相关要点表格,整理清晰且便于记忆:


MySQL索引相关要点总结表

分类 要点 说明/示例
索引有效性问题 1. 查询条件不匹配索引列 索引列未出现在WHEREJOIN等条件中,无法命中索引
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. 不是越多越好,需合理选择 根据查询模式和业务需求,权衡读写性能与资源消耗

关键记忆点

  1. 索引有效性的核心

    • 索引能否命中取决于查询条件、字段基数、表大小及统计信息准确性。
    • EXPLAIN 是排查索引是否生效的利器,重点关注type(是否为index/range)、key(是否命中索引)、rows(估算扫描行数)。
  2. 建索引的黄金法则

    • 避免冗余:仅对高频查询条件、排序/分组字段建索引。
    • 联合索引:多条件查询用联合索引(遵循最左匹配原则)。
    • 慎用长字段和低基数字段:避免浪费资源。
  3. 索引数量的平衡

    • 读写权衡:索引越多,读快写慢。
    • 合理规划:根据业务场景选择,而非盲目追求“全索引”。

示例场景

  • 无效索引示例
    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 的类型转换机制(如 ConversionServicePropertyEditor
线程安全 部分方法(如 BeanUtils 的静态方法)可能不线程安全,需注意缓存问题 默认线程安全,与 Spring 的 BeanWrapper 集成更可靠
适用场景 独立于 Spring 的项目,需基础的 Bean 属性操作(如属性复制、动态访问) Spring 生态系统内,需与 Spring 管理的 Bean(如依赖注入后的 Bean)交互
异常处理 抛出 IllegalAccessExceptionInvocationTargetException 等反射异常 抛出 IllegalArgumentExceptionBeanInstantiationException 等 Spring 特定异常
额外功能 支持通过 PropertyDescriptor 描述 Bean 属性,提供更底层的反射操作 支持 @PostConstruct 等注解的处理,与 Spring 的生命周期管理集成
性能 纯反射实现,性能较低(尤其在频繁调用时) 通过 BeanWrapper 缓存反射信息,性能更高
维护状态 Apache Commons 项目活跃,但更新较慢(Apache 项目通用节奏) Spring 持续维护,更新频繁,与 Spring 版本强绑定
推荐使用情况 非 Spring 项目,或需轻量级的 Bean 操作(如属性复制、动态访问) Spring Boot/Spring MVC 项目,需与 Spring 管理的 Bean 集成

关键差异总结

  1. 依赖与集成

    • Apache Commons:独立于 Spring,适合非 Spring 项目。
    • Spring:依赖 Spring 框架,适合 Spring 生态系统。
  2. 性能

    • SpringBeanUtils 通过 BeanWrapper 缓存反射信息,性能更优。
    • Apache Commons 纯反射实现,频繁调用时性能较差。
  3. 类型转换

    • Spring 内置更强大的类型转换机制(如 ConversionService),支持复杂类型转换。
    • Apache Commons 需手动注册转换器,灵活性较低。
  4. 线程安全

    • SpringBeanUtils 更安全,与 BeanWrapper 集成后避免缓存问题。
    • Apache Commons 静态方法可能因缓存导致线程不安全。
  5. 扩展性

    • Spring 支持与 Spring 的生命周期管理(如 @PostConstruct)、依赖注入等深度集成。
    • Apache Commons 提供基础反射操作,但功能较单一。

对比

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 指定关联查询的加载方式(EAGERLAZY)。 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 指定关联查询的加载方式(EAGERLAZY)。 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>
适用场景 用户-地址、订单-支付信息等 用户-订单、分类-商品等

注意事项

  1. 必须与 @Results 结合使用@One@Many 需要嵌套在 @Result 中,并通过 @Results 注解定义在 Mapper 方法上。
  2. 参数传递
    • column:直接使用主表单个字段名。
    • parentColumn:使用多个字段名(如 parentColumn = "user_id, region_id")。
  3. 延迟加载(Lazy Load)
    • 需要配置 MyBatis 的延迟加载支持(如 configuration.setLazyLoadingEnabled(true))。