Java实体类应封装业务语义而非镜像数据库,通过ORM显式映射(如MyBatis下划线转驼峰、Hibernate @Column)、统一主键/时间/枚举处理,并用Liquibase管理DDL变更,复杂查询切原生SQL,事务需结合MySQL锁机制设计。

Java 实体类如何与 MySQL 表结构对齐
实体类不是数据库表的镜像,而是业务语义的封装。直接让 User 类字段一一对应 user 表字段(比如都叫 user_name)会埋下耦合隐患。更合理的方式是:数据库用下划线命名(created_at),Java 用驼峰(createdAt),靠 ORM 框架自动映射。
MyBatis 默认支持下划线转驼峰(需开启 mapUnderscoreToCamelCase=true),Hibernate 则通过 @Column(name = "updated_by") 显式绑定。别依赖“自动猜”,尤其当字段名含缩写(如 usr_id → usrId 还是 userId?)时,显式声明更稳。
- MySQL 建表时主键统一用
id(BIGINT UNSIGNED AUTO_INCREMENT),Java 实体用Long id,避免int溢出 - 时间字段统一用
DATETIME(非TIMESTAMP),Java 用LocalDateTime;时区由应用层统一处理,数据库不存时区信息 - 枚举字段在 MySQL 存
TINYINT或VARCHAR,Java 实体用Integer status或StatusEnum status,但必须配@Enumerated(EnumType.STRING)或自定义 TypeHandler,否则 Hibernate 可能存错值
Java 中执行 SQL 的边界在哪
ORM 不是银弹。简单 CRUD 用 JPA/Hibernate 很顺,但涉及多表聚合、窗口函数、JSON 字段解析(如 JSON_EXTRACT(data, '$.tags'))或动态条件拼接时,硬套 ORM 容易写出低效甚至错误的 SQL。这时候该切回 MyBatis 的 XML 或 @Select 注解写原生 SQL。
关键判断点:看执行计划(EXPLAIN)。如果 ORM 生成的 SQL 出现 Using filesort、Using temporary,或 JOIN 顺序明显不合理,就该手动优化。别为了“纯面向对象”牺牲可观察性和性能。
立即学习“Java免费学习笔记(深入)”;
- 复杂查询结果不强行映射到已有实体,新建 DTO 类(如
UserStatDTO),用 MyBatis 的@Results或 ResultMap 映射字段别名 - 批量插入超 1000 条时,禁用 Hibernate 的
saveAll(),改用 MyBatis 的或 JDBCaddBatch(),否则事务日志暴涨 - 禁止在 Java 层拼接 SQL 字符串(
"WHERE name LIKE '%" + keyword + "%'"),所有参数必须走预编译占位符(#{keyword}或?),防注入
MySQL DDL 变更如何同步到 Java 项目
开发中改个字段类型(VARCHAR(50) → VARCHAR(200))或加个索引,不该靠人肉改 Java 类和 SQL 脚本。要用版本化迁移工具管起来。
Liquibase 是首选:它把每次变更记为带 ID 的 XML/YAML/Java 类(如 changelog-20240501-add-index-on-user-email.xml),启动时自动比对数据库当前状态,只执行未跑过的变更。比 Flyway 更擅长处理分支合并冲突(因支持逻辑变更而非仅 SQL 文件顺序)。
- 所有 DDL 必须进 Liquibase,包括索引、外键、注释(
支持remarks属性),Java 实体类只是消费方,不参与定义 - 本地开发用 H2 内存库跑单元测试,但集成测试必须连真实 MySQL,因为 H2 对 JSON、全文索引等支持不全
- 上线前用 Liquibase 的
generateChangeLog对比生产库与最新 changelog,确认无遗漏
事务传播与 MySQL 锁行为怎么配合
@Transactional 不是魔法开关。它控制的是 Spring 的事务管理器(JDBC Connection)生命周期,而锁(行锁、间隙锁)由 MySQL 在执行 UPDATE/SELECT ... FOR UPDATE 时实际加。两者不一致就会出问题:比如方法 A 加了 @Transactional(propagation = Propagation.REQUIRES_NEW),但里面没执行任何写操作,MySQL 根本不加锁,A 方法看似“隔离”实则裸奔。
典型踩坑:秒杀场景下,用 SELECT COUNT(*) 判断库存再 UPDATE,中间有竞态窗口。正确做法是直接 UPDATE item SET stock = stock - 1 WHERE id = ? AND stock > 0,靠 MySQL 的原子性+行锁兜底,Java 层只检查 updateCount == 1。
- 读操作(
SELECT)默认不加锁,除非显式写FOR UPDATE或LOCK IN SHARE MODE;@Transactional本身不触发锁 - 高并发更新同一行时,避免在事务里调外部 HTTP 接口或发 MQ,否则锁持有时间过长,拖垮数据库吞吐
- MySQL 的
READ COMMITTED隔离级别下,间隙锁(Gap Lock)只在唯一索引等特定条件下生效,别假设“加了索引就一定锁住范围”
最常被忽略的是:MySQL 的 autocommit 默认开启,而 Spring @Transactional 仅对受管 Bean 生效。如果调用的是另一个类的非 public 方法,或用了 this.method(),事务根本不会启动——此时你写的“事务逻辑”在 MySQL 看来就是一堆独立的 auto-commit 语句。










