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 的
<select></select>XML 或
@Select注解写原生 SQL。
关键判断点:看执行计划(
EXPLAIN)。如果 ORM 生成的 SQL 出现
Using filesort、
Using temporary,或 JOIN 顺序明显不合理,就该手动优化。别为了“纯面向对象”牺牲可观察性和性能。
立即学习“Java免费学习笔记(深入)”;
复杂查询结果不强行映射到已有实体,新建 DTO 类(如UserStatDTO),用 MyBatis 的
@Results或 ResultMap 映射字段别名 批量插入超 1000 条时,禁用 Hibernate 的
saveAll(),改用 MyBatis 的
<foreach></foreach>或 JDBC
addBatch(),否则事务日志暴涨 禁止在 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,包括索引、外键、注释(
<createindex></createindex>支持
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 语句。
