Java Liquibase 实战指南:让数据库变更像代码一样可追踪

简介

Liquibase 是一个数据库变更管理工具。

它解决的问题不是:

text 复制代码
Java 怎么查询数据库
SQL 怎么映射成对象

而是:

text 复制代码
数据库结构怎么跟着项目一起演进。

比如项目从第一个版本到第三个版本,数据库可能经历这些变化:

text 复制代码
V1:创建用户表
V2:给用户表增加 email 字段
V3:创建订单表
V4:给订单表增加索引
V5:初始化系统配置数据

如果靠手动执行 SQL,很容易出现这些问题:

text 复制代码
开发库执行了,测试库没执行
测试库执行了,生产库漏了一条
同一条 SQL 被重复执行
某个字段是谁加的、什么时候加的,查不清楚
回滚时不知道该撤哪几条 SQL

Liquibase 的做法是把数据库变更写成文件,并纳入 Git 管理。

应用启动或命令执行时,Liquibase 会检查哪些变更已经执行过,哪些还没执行,然后只执行未执行的部分。

一句话概括:

text 复制代码
Liquibase 用来管理数据库变更版本,让表结构、索引、初始化数据可以像代码一样被记录、评审、执行和回滚。

Liquibase 适合什么场景

常见场景有这些:

  • Spring Boot 项目需要自动初始化数据库结构
  • 多个环境需要保持表结构一致
  • 团队协作开发,数据库变更需要走 Git 评审
  • 发布时需要知道数据库变更执行到哪一步
  • 表结构变化需要可追踪、可回滚
  • 不希望生产环境依赖 Hibernate ddl-auto: update
  • 需要同时管理 DDL 和少量初始化数据

如果项目里已经有这些文件:

text 复制代码
001_create_user_table.sql
002_add_email_column.sql
003_create_order_table.sql

Liquibase 就是把这套脚本管理得更标准、更可追踪。

Liquibase 和 ORM 的区别

Liquibase 不是 ORM。

它不负责:

  • 把 Java 对象保存到数据库
  • 把查询结果映射成实体
  • 生成业务查询 SQL
  • 管理 Repository 或 Mapper

这些事情通常由:

  • JdbcTemplate
  • MyBatis
  • MyBatis-Plus
  • Spring Data JPA

来完成。

Liquibase 负责的是数据库结构变更。

常见搭配方式:

text 复制代码
Liquibase 管表结构
MyBatis / JPA / JdbcTemplate 管业务读写

工作原理

Liquibase 的核心流程大致是:

text 复制代码
读取 changelog 文件
  |
  v
检查 DATABASECHANGELOG 表
  |
  v
找出还没执行的 changeSet
  |
  v
获取 DATABASECHANGELOGLOCK 锁
  |
  v
执行变更
  |
  v
记录执行历史

第一次运行时,Liquibase 会自动创建两张表。

DATABASECHANGELOG

这张表记录已经执行过的变更。

常见字段有:

text 复制代码
ID
AUTHOR
FILENAME
DATEEXECUTED
ORDEREXECUTED
EXECTYPE
MD5SUM
DESCRIPTION
COMMENTS
TAG
LIQUIBASE
CONTEXTS
LABELS

Liquibase 会根据这些信息判断某个 changeSet 是否已经执行过。

DATABASECHANGELOGLOCK

这张表用来加锁。

作用是防止多个应用实例同时执行数据库迁移。

在多实例部署时,这一点很重要。

changeLog 和 changeSet

changeLog 是变更日志文件。

它可以包含很多个 changeSet

changeSet 是最小执行单元。

例如:

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 001-create-user-table
      author: feng
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: bigint
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false

其中:

text 复制代码
id + author + 文件路径

共同定位一个 changeSet

已经执行过的 changeSet 会记录到 DATABASECHANGELOG,后续不会重复执行。

Maven 依赖

Spring Boot 项目里引入 liquibase-core

xml 复制代码
<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

如果项目使用 JDBC:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

如果项目使用 JPA:

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

数据库驱动以 MySQL 为例:

xml 复制代码
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

Spring Boot 会根据 classpath 里的 liquibase-core 自动配置 SpringLiquibase

启动时会自动执行未执行的数据库变更。

Spring Boot 配置

application.yml 示例:

yaml 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/liquibase_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  liquibase:
    enabled: true
    change-log: classpath:db/changelog/db.changelog-master.yaml
    contexts: dev

常见配置:

配置 作用
spring.liquibase.enabled 是否启用 Liquibase
spring.liquibase.change-log 主 changelog 文件位置
spring.liquibase.contexts 指定执行哪些 context
spring.liquibase.default-schema 默认 schema
spring.liquibase.drop-first 执行前是否先删除对象
spring.liquibase.rollback-file 输出 rollback SQL 文件

drop-first 只适合极少数临时测试场景。

常规项目里通常保持默认关闭。

如果同时使用 JPA,生产环境一般这样配置:

yaml 复制代码
spring:
  jpa:
    hibernate:
      ddl-auto: none

表结构交给 Liquibase 管理,避免 ORM 自动改表和 changelog 冲突。

推荐目录结构

一种常见目录结构:

text 复制代码
src/main/resources/
└── db/
    └── changelog/
        ├── db.changelog-master.yaml
        ├── v1.0.0/
        │   ├── 001-create-users-table.yaml
        │   ├── 002-create-orders-table.yaml
        │   └── 003-init-user-data.yaml
        ├── v1.1.0/
        │   └── 001-add-user-status-column.yaml
        └── sql/
            └── 001-create-report-view.sql

主文件只负责组织。

具体变更放到版本目录里。

这样发布历史会比较清楚。

主 changelog

db.changelog-master.yaml

yaml 复制代码
databaseChangeLog:
  - include:
      file: db/changelog/v1.0.0/001-create-users-table.yaml
  - include:
      file: db/changelog/v1.0.0/002-create-orders-table.yaml
  - include:
      file: db/changelog/v1.0.0/003-init-user-data.yaml
  - include:
      file: db/changelog/v1.1.0/001-add-user-status-column.yaml

也可以使用 includeAll

yaml 复制代码
databaseChangeLog:
  - includeAll:
      path: db/changelog/v1.0.0/

include 的顺序更明确。

includeAll 更省事,但要注意文件名排序。

实际项目里,很多团队会选择:

text 复制代码
主文件 include 版本文件
版本文件 include 具体 changeSet 文件

创建用户表

v1.0.0/001-create-users-table.yaml

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 001-create-users-table
      author: feng
      changes:
        - createTable:
            tableName: users
            columns:
              - column:
                  name: id
                  type: bigint
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: username
                  type: varchar(50)
                  constraints:
                    nullable: false
              - column:
                  name: email
                  type: varchar(100)
                  constraints:
                    nullable: false
              - column:
                  name: age
                  type: int
                  constraints:
                    nullable: false
              - column:
                  name: created_at
                  type: datetime
                  constraints:
                    nullable: false
        - addUniqueConstraint:
            tableName: users
            columnNames: email
            constraintName: uk_users_email
      rollback:
        - dropTable:
            tableName: users

这个 changeSet 做了两件事:

  • 创建 users
  • email 加唯一约束

回滚时删除 users 表。

创建订单表

v1.0.0/002-create-orders-table.yaml

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 002-create-orders-table
      author: feng
      changes:
        - createTable:
            tableName: orders
            columns:
              - column:
                  name: id
                  type: bigint
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: user_id
                  type: bigint
                  constraints:
                    nullable: false
              - column:
                  name: order_no
                  type: varchar(50)
                  constraints:
                    nullable: false
              - column:
                  name: amount
                  type: decimal(10,2)
                  constraints:
                    nullable: false
              - column:
                  name: status
                  type: varchar(20)
                  constraints:
                    nullable: false
              - column:
                  name: created_at
                  type: datetime
                  constraints:
                    nullable: false
        - createIndex:
            tableName: orders
            indexName: idx_orders_user_id
            columns:
              - column:
                  name: user_id
        - addForeignKeyConstraint:
            baseTableName: orders
            baseColumnNames: user_id
            referencedTableName: users
            referencedColumnNames: id
            constraintName: fk_orders_user_id
      rollback:
        - dropTable:
            tableName: orders

这里包含:

  • 建表
  • 创建索引
  • 创建外键

实际项目里,如果外键由应用层保证,也可以不创建数据库外键,只保留索引。

初始化数据

v1.0.0/003-init-user-data.yaml

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 003-init-user-data
      author: feng
      context: dev,test
      changes:
        - insert:
            tableName: users
            columns:
              - column:
                  name: username
                  value: 张三
              - column:
                  name: email
                  value: zhangsan@example.com
              - column:
                  name: age
                  valueNumeric: 20
              - column:
                  name: created_at
                  valueDate: 2026-01-01T10:00:00
        - insert:
            tableName: users
            columns:
              - column:
                  name: username
                  value: 李四
              - column:
                  name: email
                  value: lisi@example.com
              - column:
                  name: age
                  valueNumeric: 25
              - column:
                  name: created_at
                  valueDate: 2026-01-02T10:00:00
      rollback:
        - delete:
            tableName: users
            where: email in ('zhangsan@example.com', 'lisi@example.com')

这里用了:

yaml 复制代码
context: dev,test

表示这批数据只在 devtest 环境执行。

如果生产环境配置:

yaml 复制代码
spring:
  liquibase:
    contexts: prod

这组测试数据不会执行。

新增字段

v1.1.0/001-add-user-status-column.yaml

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 001-add-user-status-column
      author: feng
      changes:
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: status
                  type: varchar(20)
                  defaultValue: ACTIVE
                  constraints:
                    nullable: false
      rollback:
        - dropColumn:
            tableName: users
            columnName: status

这类变更很常见:

text 复制代码
已有表增加一个业务字段。

如果表数据很多,新增非空字段时要谨慎。

常见做法是拆成几步:

text 复制代码
先加可空字段
回填数据
再加非空约束

使用 SQL 格式

有些场景直接写 SQL 更直观。

例如创建视图、函数、复杂索引。

db/changelog/sql/001-create-report-view.sql

sql 复制代码
-- liquibase formatted sql

-- changeset feng:001-create-report-view
create view v_user_order_summary as
select
  u.id as user_id,
  u.username,
  count(o.id) as order_count,
  coalesce(sum(o.amount), 0) as total_amount
from users u
left join orders o on o.user_id = u.id
group by u.id, u.username;

-- rollback drop view v_user_order_summary;

主 changelog 引入:

yaml 复制代码
databaseChangeLog:
  - include:
      file: db/changelog/sql/001-create-report-view.sql

SQL 格式适合:

  • 视图
  • 存储过程
  • 函数
  • 触发器
  • 数据库特有语法
  • DBA 提供的变更脚本

使用 XML 格式

XML 格式也很常见。

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.liquibase.org/xml/ns/dbchangelog
            https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">

    <changeSet id="001-create-product-table" author="feng">
        <createTable tableName="products">
            <column name="id" type="bigint" autoIncrement="true">
                <constraints primaryKey="true" nullable="false"/>
            </column>
            <column name="name" type="varchar(100)">
                <constraints nullable="false"/>
            </column>
            <column name="price" type="decimal(10,2)">
                <constraints nullable="false"/>
            </column>
        </createTable>
        <rollback>
            <dropTable tableName="products"/>
        </rollback>
    </changeSet>
</databaseChangeLog>

YAML、XML、JSON、SQL 都可以使用。

项目里最好统一主格式。

复杂 SQL 再单独使用 formatted SQL 文件。

preConditions 前置条件

preConditions 可以在执行前检查数据库状态。

例如表不存在时才创建:

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 004-create-config-table
      author: feng
      preConditions:
        - onFail: MARK_RAN
        - not:
            - tableExists:
                tableName: app_config
      changes:
        - createTable:
            tableName: app_config
            columns:
              - column:
                  name: id
                  type: bigint
                  autoIncrement: true
                  constraints:
                    primaryKey: true
                    nullable: false
              - column:
                  name: config_key
                  type: varchar(100)
                  constraints:
                    nullable: false
              - column:
                  name: config_value
                  type: varchar(500)
      rollback:
        - dropTable:
            tableName: app_config

onFail: MARK_RAN 的含义是:

text 复制代码
条件不满足时,把这个 changeSet 标记为已执行。

常见处理方式还有:

  • HALT:停止执行
  • WARN:输出警告
  • CONTINUE:继续执行
  • MARK_RAN:标记为已执行

context 和 label

context 常用于区分环境。

yaml 复制代码
changeSet:
  id: init-dev-data
  author: feng
  context: dev

配置:

yaml 复制代码
spring:
  liquibase:
    contexts: dev

只有匹配 dev 的 changeSet 会执行。

label 更适合按功能或发布范围筛选。

yaml 复制代码
changeSet:
  id: add-order-module
  author: feng
  labels: order-module

执行时可以只跑某些 label。

简单理解:

text 复制代码
context 更偏环境
label 更偏业务范围或发布范围

rollback 回滚

Liquibase 支持为 changeSet 编写回滚逻辑。

YAML 示例:

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 005-add-user-phone-column
      author: feng
      changes:
        - addColumn:
            tableName: users
            columns:
              - column:
                  name: phone
                  type: varchar(30)
      rollback:
        - dropColumn:
            tableName: users
            columnName: phone

formatted SQL 示例:

sql 复制代码
-- liquibase formatted sql

-- changeset feng:005-add-user-phone-column
alter table users add column phone varchar(30);

-- rollback alter table users drop column phone;

回滚前通常先生成 SQL 预览,再执行真正回滚。

常用命令

如果使用 Liquibase CLI,常见命令如下。

执行未执行变更:

bash 复制代码
liquibase update

查看未执行变更:

bash 复制代码
liquibase status

生成即将执行的 SQL:

bash 复制代码
liquibase update-sql

回滚最近 1 个 changeSet:

bash 复制代码
liquibase rollback-count 1

生成回滚 SQL:

bash 复制代码
liquibase rollback-count-sql 1

打标签:

bash 复制代码
liquibase tag v1.0.0

回滚到标签:

bash 复制代码
liquibase rollback v1.0.0

查看变更历史:

bash 复制代码
liquibase history

Maven 插件

也可以使用 Maven 插件。

xml 复制代码
<plugin>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-maven-plugin</artifactId>
    <version>5.0.1</version>
    <configuration>
        <changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
        <url>jdbc:mysql://localhost:3306/liquibase_demo</url>
        <username>root</username>
        <password>123456</password>
        <driver>com.mysql.cj.jdbc.Driver</driver>
    </configuration>
</plugin>

常用命令:

bash 复制代码
mvn liquibase:update
mvn liquibase:status
mvn liquibase:updateSQL
mvn liquibase:rollback -Dliquibase.rollbackCount=1

实际项目里,Spring Boot 启动自动执行和 CI/CD 命令执行二选一即可。

团队规模较大时,数据库迁移经常放在发布流水线里执行。

和 Flyway 的区别

FlywayLiquibase 都是数据库迁移工具。

对比项 Liquibase Flyway
变更格式 YAML、XML、JSON、SQL 主要是 SQL
抽象变更 支持 createTableaddColumn 以 SQL 脚本为主
回滚 社区版也支持手写 rollback 常见做法是写反向迁移
学习成本 稍高 较低
灵活性
适合场景 复杂变更、多格式、需要回滚定义 SQL 优先、简单直接

粗略理解:

text 复制代码
Flyway 更像按顺序执行 SQL 文件
Liquibase 更像用 changeSet 管理数据库变更记录

如果团队喜欢纯 SQL,Flyway 会很顺手。

如果需要 YAML/XML、precondition、context、label、rollback,Liquibase 更合适。

和 JPA ddl-auto 的关系

JPA 有:

yaml 复制代码
spring:
  jpa:
    hibernate:
      ddl-auto: update

它可以根据实体自动改表。

这个能力适合本地快速验证。

但在多人协作和生产环境里,自动改表不容易审计。

使用 Liquibase 后,推荐让数据库结构由 changelog 管理:

yaml 复制代码
spring:
  jpa:
    hibernate:
      ddl-auto: validate

或者:

yaml 复制代码
spring:
  jpa:
    hibernate:
      ddl-auto: none

这样表结构变化都能在 Git 里看到。

实战 Demo:配合 Spring Data JPA

实体类:

java 复制代码
package com.example.demo.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 50)
    private String username;

    @Column(nullable = false, unique = true, length = 100)
    private String email;

    @Column(nullable = false)
    private Integer age;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    // getter setter
}

Repository:

java 复制代码
package com.example.demo.repository;

import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}

Service:

java 复制代码
package com.example.demo.service;

import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;

@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Transactional
    public Long create(String username, String email, Integer age) {
        User user = new User();
        user.setUsername(username);
        user.setEmail(email);
        user.setAge(age);
        user.setCreatedAt(LocalDateTime.now());

        User saved = userRepository.save(user);
        return saved.getId();
    }
}

这里的表结构由 Liquibase changelog 创建。

业务读写由 Spring Data JPA 完成。

两者分工很清楚。

常见使用建议

已执行的 changeSet 保持稳定

changeSet 执行后,Liquibase 会记录 checksum。

如果直接修改已执行的 changeSet,后续运行可能出现 checksum 校验问题。

更稳妥的方式是:

text 复制代码
新增一个 changeSet 修正前面的结构。

例如字段长度从 varchar(50) 改成 varchar(100)

yaml 复制代码
databaseChangeLog:
  - changeSet:
      id: 006-modify-user-username-length
      author: feng
      changes:
        - modifyDataType:
            tableName: users
            columnName: username
            newDataType: varchar(100)
      rollback:
        - modifyDataType:
            tableName: users
            columnName: username
            newDataType: varchar(50)

changeSet 保持小而清楚

一个 changeSet 最好表达一个明确变更。

例如:

text 复制代码
创建 users 表
给 users 添加 status 字段
创建 orders 表
创建 idx_orders_user_id 索引
初始化字典数据

这样出问题时更容易定位,也更容易写 rollback。

生产环境先预览 SQL

正式执行前可以先生成 SQL:

bash 复制代码
liquibase update-sql

或者 Maven:

bash 复制代码
mvn liquibase:updateSQL

生成的 SQL 可以给 DBA 或团队评审。

初始化数据区分环境

测试账号、演示数据、开发配置,建议加 context

yaml 复制代码
context: dev,test

生产环境只执行:

yaml 复制代码
spring:
  liquibase:
    contexts: prod

这样可以避免测试数据进入生产库。

大表变更要拆步骤

大表上直接执行这些操作可能耗时较长:

  • 增加非空字段
  • 修改字段类型
  • 添加唯一索引
  • 大批量 update
  • 大批量 delete

更常见的做法是拆分:

text 复制代码
先加可空字段
应用兼容新旧字段
分批回填数据
再加约束或切换读取逻辑

Liquibase 可以记录每一步,但具体执行策略仍然要结合数据库规模和业务窗口。

多实例启动注意锁

DATABASECHANGELOGLOCK 会防止多个实例同时迁移。

如果某次迁移异常中断,锁可能没有释放。

这时需要先确认没有迁移任务正在执行,再处理锁表。

处理锁表前,先确认没有迁移任务仍在执行。

常用 change 类型汇总

change 类型 作用
createTable 创建表
dropTable 删除表
addColumn 新增字段
dropColumn 删除字段
modifyDataType 修改字段类型
renameColumn 重命名字段
createIndex 创建索引
dropIndex 删除索引
addUniqueConstraint 添加唯一约束
addForeignKeyConstraint 添加外键
insert 插入数据
update 更新数据
delete 删除数据
sql 执行原生 SQL
loadData 从 CSV 加载数据

常用配置汇总

配置 作用
spring.liquibase.enabled 是否启用
spring.liquibase.change-log 主 changelog 路径
spring.liquibase.contexts 执行指定 context
spring.liquibase.label-filter 执行指定 label
spring.liquibase.default-schema 默认 schema
spring.liquibase.liquibase-schema Liquibase 记录表所在 schema
spring.liquibase.drop-first 执行前先删除数据库对象
spring.liquibase.rollback-file 生成 rollback SQL 文件

总结

Liquibase 的重点是把数据库变更纳入工程化管理。

常见使用流程是:

text 复制代码
创建 master changelog
按版本拆分 changelog 文件
每次表结构变化新增 changeSet
启动应用或流水线执行 update
通过 DATABASECHANGELOG 追踪执行历史
必要时使用 rollback 或新增修复 changeSet

它适合这些场景:

  • Spring Boot 项目需要稳定初始化表结构
  • 多环境数据库结构需要保持一致
  • 数据库变更需要代码评审
  • 发布过程需要自动执行数据库迁移
  • 表结构变化需要可追踪、可回滚

落地时重点关注几件事:

  • changelog 文件纳入 Git
  • 已执行 changeSet 不直接修改
  • 大表变更拆步骤
  • 生产环境先预览 SQL
  • 测试数据使用 context 隔离
  • JPA ddl-auto 不和 Liquibase 抢表结构控制权

掌握这些内容后,Liquibase 已经可以覆盖大多数 Java 项目的数据库版本管理需求。

相关推荐
qq_422152571 小时前
PDF 解密工具怎么选?2026 年文档密码移除方案与注意事项
java·前端·pdf
布朗克1681 小时前
38 Spring Boot入门——自动配置、核心注解与Starter机制
java·spring boot·后端
沪漂阿龙1 小时前
LangChain 系列:Structured Output结构化输出与源码解析
java·人工智能·架构·langchain
半夜燃烧的香烟1 小时前
springboot3.0 集成minio上传文件,支持多个桶名
java·开发语言·spring boot
J2虾虾2 小时前
Android支持Java语言的标准
android·java·开发语言
Oo_行者_oO2 小时前
Spring Schedule + ShedLock + RabbitMQ 生产级落地方案 - 云楼(中国)
java·后端
AI人工智能+电脑小能手2 小时前
【大白话说Java面试题 第113题】【并发篇】第13题:说一下乐观锁的优点和缺点?
java·开发语言·面试
Mahir082 小时前
HashMap 底层原理深度解密:从数据结构到 JDK1.7/1.8 演进全解
java·后端·面试·hashmap