资讯动态

Spring Boot非JPA整合Hibernate:SessionFactory原生配置实战

发布时间:2026/10/17 9:56:33 来源:尧图企业网站定制
说实话每次有人在群里问“Spring Boot 能不能不用 JPA 那套直接用 Hibernate”我都会先反问一句你说的“JPA”是指 Spring Data JPA还是指底层的 EntityManager因为大多数教程把 Hibernate 和 JPA 彻底捆绑在一起讲导致很多人误以为只要用 Hibernate 就得走持久化上下文那套规范。其实完全不是一回事。我手上的一个老项目用 Spring Boot 3.x 重新梳理数据层团队明确要求不要引入 Spring Data JPA也不要走EntityManager而是希望用org.hibernate.Session/SessionFactory这套原生 API。当时网上找到的文章要么是教你怎么配LocalContainerEntityManagerFactoryBean要么就是用 Spring Data 的CrudRepository真正“非 JPA”的注解配置案例少得可怜。这篇文章就是我整理完那个项目后留下的笔记。标题里的“非 JPA”大家先别理解偏了实体上的Entity、Table这种注解照样用因为 Hibernate 6 已经完全基于 Jakarta Persistence 注解来驱动映射。真正的区别在于——我们不创建EntityManagerFactory不依赖 Spring Data 的 repository而是自己声明LocalSessionFactoryBean将SessionFactory注入业务层用Session完成增删改查和事务控制。如果你属于这几类情况这篇文章值得看完第一旧项目从 SSM 或原生态 Hibernate 迁移过来不想把 DAO 层推到重写第二需要用到 Hibernate 自带的Where、Formula、FilterDef、SQLDelete这类专属能力而这些用标准 JPA 会非常别扭第三团队本身对 HQL 和原生 SQL 更熟不想被 repository 的封装挡住调试视线。1. 为什么“非 JPA”仍然值得做先搞懂 Hibernate 和 JPA 的分工1.1 JPA 是规范Hibernate 是实现但实现明显比规范多很多人以为 Hibernate 就是 JPAJPA 就是 Hibernate。严格说JPA 是一套由jakarta.persistence包定义的规范它规定了实体映射、EntityManager、JPQL、事务等标准接口Hibernate 是这套规范最常见的实现之一但 Hibernate 在实现规范之外又额外提供了一大堆自己的 API 和注解。所以当你用原生 Hibernate 时Entity、Table、Id这些仍然是 Jakarta Persistence 的注解不是 Hibernate 发明的。真正属于 Hibernate 的是org.hibernate.annotations这个包下的东西比如Formula、Where、GenerationTime、NaturalId等等。“非 JPA”这句话更准确的理解是基础注解跟随 JPA 标准但 API 和容器装配全程走 Hibernate 自己的 SessionFactory。1.2 什么项目会主动绕开 EntityManager先说一个最常见的业务场景。某个运营系统原先用的是 SSH 或者 SSM 三段式架构DAO 类里到处是sessionFactory.getCurrentSession()整整积累了七八年。现在要迁到 Spring Boot如果硬切 Spring Data JPA等于要把几十个 DAO 全部重写一遍而且还得重新梳理事务边界。这种项目里原生 Hibernate 天然是嫁接最平滑的方案。再说一个技术选型上的理由。标准 JPA 对实体的控制力度偏“标准”而业务上常见的一些骚操作比如软删除的时候把DELETE语句改成逻辑删除的UPDATE语句或者某个字段不落到数据库表里、而是用 SQL 子查询实时算出来这些能力用 Hibernate 自带的注解可以写得很优雅放在纯 JPA 标准里就只能靠事件监听器或者自己造轮子。还有一些情况是团队技术栈决定的。有些人调 HQL 调习惯了一条复杂查询怎么写、缓存怎么控制心里门清但换到 JPA 的 Criteria 或者 Spring Data 的 Specification总觉得绕了一层布排查问题时还得解包。信息透明带来的调试效率有时候比“标准”本身更值钱。1.3 反过来也要劝退一些人不是所有项目都适合非 JPA。如果你的业务只是简单 CRUD甚至百分之八十的查询都是单表操作那我强烈建议直接使用 Spring Data JPA。它的 automatic implementation 能把 DAO 代码量压缩到令人发指的地步而且社区资料极多新手上手成本低。原生 Hibernate 适合的是“资源可控、踩坑能力足够”的团队。盲目跟风原生 API等于主动放弃了一个成熟生态里的省心配置。先把适用边界划清楚后面实操起来心里才有底。2. 依赖清单与自动配置第一步最容易翻车2.1 Maven 里别再引入 starter-data-jpa既然目标是非 JPAspring-boot-starter-data-jpa这个依赖最好直接不出现。它不是 JPA 规范本身而是 Spring Data 在 JPA 之上的自动配置包里面包含了JpaRepositoriesAutoConfiguration、HibernateJpaAutoConfiguration一堆自动装配逻辑我们并不需要它们。我当时的最小依赖是这样列的。parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.2.5/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdorg.springframework/groupId artifactIdspring-orm/artifactId /dependency dependency groupIdorg.hibernate.orm/groupId artifactIdhibernate-core/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency /dependencies这里三个细节很关键很多人就是栽在这里。第一个细节是spring-boot-starter-jdbc不能省。虽然我们用的是 Hibernate但数据源本身还是要 Spring Boot 的DataSourceAutoConfiguration来创建它会给一个 HikariCP 连接池。hibernate-core不会自动帮你管理连接池它只知道从DataSource拿连接。第二个细节是spring-orm必须手工加。LocalSessionFactoryBean不是 Hibernate 里的类而是 Spring 对它做的集成封装位于org.springframework.orm.hibernate5或者新版里的org.springframework.orm.hibernate6包下。如果你只依赖了hibernate-core没有spring-orm编译期直接找不到这个类。第三个细节是版本号。spring-boot-starter-parent里已经锁定了 Hibernate 6.x 的版本所以hibernate-core不用写version。这是大多数人容易忽略的地方手动指定版本反而可能跟 Spring Boot 内部的依赖管理打架。2.2 排除自动配置的时候别连数据源自动配置一起干掉了有了spring-orm和hibernate-core类路径上已经同时出现了LocalContainerEntityManagerFactoryBean和EntityManagerSpring Boot 的HibernateJpaAutoConfiguration会选择自动配置一个EntityManagerFactory。我们要明确告诉 Spring Boot我不需要你替我把 JPA 容器搭起来。在启动类上做排除是最快的办法。package com.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; SpringBootApplication(exclude HibernateJpaAutoConfiguration.class) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }注意这里只需要排除HibernateJpaAutoConfiguration。JpaRepositoriesAutoConfiguration在没有 Spring Data JPA 的类路径时根本不会生效不需要管它。DataSourceAutoConfiguration千万别排除否则数据源都没了SessionFactory 更无从接起。有同学会问如果我的工程里已经引入了spring-boot-starter-data-jpa现在突然要切非 JPA怎么办理论上你可以在启动类里同时排除掉HibernateJpaAutoConfiguration和JpaRepositoriesAutoConfiguration但更干净的做法还是把依赖证据从pom.xml里彻底删掉。遗留依赖除了带来自动配置风险还会影响 Maven 依赖树分析。2.3 配置文件里只需要数据源application.yml保持朴素不需要写任何spring.jpa开头的配置项。spring: datasource: url: jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver为什么不用写spring.jpa.hibernate.ddl-auto因为后面我们会在 Hibernate 配置类里直接设置hibernate.hbm2ddl.auto那些spring.jpa.*配置项主要消费在 JPA 自动配置链路里我们的链路不经过那里写了也是白写。这个细节很容易被网上那些混合教程搞糊涂照着 JPA 教程写spring.jpa.show-sqltrue结果发现日志里啥也没有因为配置根本没进 SessionFactory。3. 配置 SessionFactory 和事务管理器注解扫描从这一步开始3.1 声明 LocalSessionFactoryBean而不是 LocalContainerEntityManagerFactoryBean这是全文的核心。配置类可以直接把DataSource注入进来交给LocalSessionFactoryBean使用。package com.demo.config; import java.util.Properties; import javax.sql.DataSource; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.hibernate5.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.hibernate.SessionFactory; import org.springframework.orm.hibernate5.HibernateTransactionManager; Configuration EnableTransactionManagement public class HibernateConfig { Bean public LocalSessionFactoryBean sessionFactory(DataSource dataSource) { LocalSessionFactoryBean factory new LocalSessionFactoryBean(); factory.setDataSource(dataSource); factory.setPackagesToScan(com.demo.entity); factory.setHibernateProperties(hibernateProperties()); return factory; } Bean public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) { return new HibernateTransactionManager(sessionFactory); } private Properties hibernateProperties() { Properties props new Properties(); props.setProperty(hibernate.dialect, org.hibernate.dialect.MySQLDialect); props.setProperty(hibernate.show_sql, true); props.setProperty(hibernate.format_sql, true); props.setProperty(hibernate.hbm2ddl.auto, update); props.setProperty(hibernate.connection.autocommit, false); return props; } }如果你工程里同时引用了 Hibernate 6 和 Spring 6包名可能会是org.springframework.orm.hibernate6具体看 Spring ORM 版本。我的项目用的是 Spring Boot 3.2.5它内部的 spring-orm 对应的是 hibernate6 扩展所以实际写的时候我把包名换成了org.springframework.orm.hibernate6.LocalSessionFactoryBean和org.springframework.orm.hibernate6.HibernateTransactionManager。大家根据自己 IDE 里实际 import 到的包名来别照抄完发现类报红。setPackagesToScan说的是实体包路径它会自动扫描该包及其子包下所有带Entity的类。如果你手头实体类比较零散也可以用factory.setAnnotatedClasses(UserInfo.class, MemberInfo.class, ...)显式列出来。工程项目里我更建议setPackagesToScan后面新增实体不用改配置类。3.2 几个 Hibernate 属性的实际意义很多人配 Hibernate 属性是照抄我把几个容易理解偏差的说一下。hibernate.hbm2ddl.autoupdate表示应用启动时会根据实体映射自动更新数据库表结构。开发阶段确实方便改个字段加个列重启就能同步。但在生产环境这是个隐患如果线上库表数据量大Hibernate 生成的 ALTER 语句很可能不是你要的更严重的还会把字段类型改掉。我的实践是测试环境用update生产环境一律validate或none表结构变更交给数据库迁移脚本统一管理。hibernate.connection.autocommitfalse很多人不写。它是提醒 Hibernate 不要依赖连接的自动提交事务边界交给 Spring 的事务管理器统一控制。如果不写某些数据库驱动默认 autocommit 打开可能出现一个 Session 里执行多条 SQL各自提交事务形同虚设。我额外解释一下为什么事务管理器必须用HibernateTransactionManager而不是 Spring 自带的DataSourceTransactionManager。因为HibernateTransactionManager会把当前 Session 和 Spring 的事务同步器绑定在一起这样你在业务方法里调用sessionFactory.getCurrentSession()时拿到的就是当前事务对应的同一个 Session。如果用了DataSourceTransactionManagerSession 和事务各管各的你就会碰到“no current session”或者“session closed”这类诡异问题。3.3 用 CommandLineRunner 快速验证实体扫描是否成功配置写完先别急着写 Service找一个最简单的验证方式。我一般在启动类里临时注册一个CommandLineRunner把 SessionFactory 里面已经扫描到的实体打印出来。package com.demo; import org.hibernate.SessionFactory; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; import org.springframework.context.annotation.Bean; SpringBootApplication(exclude HibernateJpaAutoConfiguration.class) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } Bean public CommandLineRunner checkSessionFactory(SessionFactory sessionFactory) { return args - { sessionFactory.getMetamodel().getEntities().forEach(System.out::println); }; } }启动之后如果控制台能看到EntityTypeImpl{namecom.demo.entity.UserInfo}这样的信息说明LocalSessionFactoryBean已经把注解扫描跑通了。如果什么都打印不出来先回到两个检查点实体类是不是真的在setPackagesToScan指定的包下实体类上有没有Entity注解。这两个问题我至少见过三个人卡了半天。4. 实体注解JPA 标准注解负责骨架Hibernate 专属注解负责业务魔法4.1 一个能跑起来的实体类长什么样还是拿最常见的用户表举例。实体类上的基础注解比如Entity、Table、Id、GeneratedValue实际上全部来自jakarta.persistence包这一点和非 JPA 并不冲突。package com.demo.entity; import java.time.LocalDateTime; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.DynamicUpdate; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; Entity Table(name user_info) DynamicUpdate public class UserInfo { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name username, nullable false, length 64) private String username; Column(name email, length 128) private String email; Column(name created_at, updatable false) CreationTimestamp private LocalDateTime createdAt; public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getUsername() { return username; } public void setUsername(String username) { this.username username; } public String getEmail() { return email; } public void setEmail(String email) { this.email email; } public LocalDateTime getCreatedAt() { return createdAt; } public void setCreatedAt(LocalDateTime createdAt) { this.createdAt createdAt; } }注意CreationTimestamp这个注解来自org.hibernate.annotations目的是让 Hibernate 在insert的时候自动填LocalDateTime不用我们在业务代码里手动 set。它和 JPA 里PrePersist监听器能达到类似效果但写法简洁得多。4.2 Hibernate 专属注解的大杀器清单真正让原生 Hibernate 值得用的是下面这几个表里列出的注解。它们单独任何一个拿出来都能少写几十行业务代码。注解包典型使用场景注意事项Whereorg.hibernate.annotations软删除查询时自动附带 deleted0 条件对关联集合也能生效但复杂 HQL join 下容易引起迷惑Formulaorg.hibernate.annotations实体里加一个只读的统计字段例如订单数对应于 SQL 片段不走缓存频繁查询要评估性能SQLDeleteorg.hibernate.annotations调用 delete 时改成逻辑删除 UPDATE通常和Where搭配使用NaturalIdorg.hibernate.annotations业务唯一键例如手机号可以用session.byNaturalId()直接加载DynamicUpdateorg.hibernate.annotations更新时只生成变化字段的 UPDATE 语句有少量动态 SQL 拼装开销不适合超高频简单更新DynamicInsertorg.hibernate.annotations插入时只对有值的字段生成 INSERTHibernate 6 里语义变化要小心CreationTimestamp/UpdateTimestamporg.hibernate.annotations创建时间、更新时间自动填入不要和数据库默认值混淆Fetchorg.hibernate.annotations控制关联集合的加载策略比如 SUBSELECT使用不当会导致 SQL 数量不可控随便举一个组合使用案例。会员实体希望做到逻辑删除同时数据表里顺便存一个会员的累计下单数下边这种写法就非常干净。package com.demo.entity; import java.time.LocalDateTime; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.DynamicUpdate; import org.hibernate.annotations.Formula; import org.hibernate.annotations.NaturalId; import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; Entity Table(name member_info) SQLDelete(sql UPDATE member_info SET deleted 1 WHERE id ?) Where(clause deleted 0) DynamicUpdate public class MemberInfo { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; NaturalId Column(name mobile, nullable false, length 20) private String mobile; Column(name real_name, length 64) private String realName; Formula((select count(o.id) from order_info o where o.member_id id and o.deleted 0)) private Long orderCount; Column(name deleted) private Integer deleted 0; Column(name create_time, updatable false) CreationTimestamp private LocalDateTime createTime; public Long getId() { return id; } public void setId(Long id) { this.id id; } public String getMobile() { return mobile; } public void setMobile(String mobile) { this.mobile mobile; } public String getRealName() { return realName; } public void setRealName(String realName) { this.realName realName; } public Long getOrderCount() { return orderCount; } public void setOrderCount(Long orderCount) { this.orderCount orderCount; } public Integer getDeleted() { return deleted; } public void setDeleted(Integer deleted) { this.deleted deleted; } public LocalDateTime getCreateTime() { return createTime; } public void setCreateTime(LocalDateTime createTime) { this.createTime createTime; } }这个实体类里有三个非常实用的小心机。第一SQLDelete把 Hibernate 的delete操作拦截成UPDATE member_info SET deleted 1 WHERE id ?你再也不用在 DAO 里手动写软删除语句了。Where又保证普通查询自动带上deleted 0查不到已经删除的数据。第二NaturalId把mobile标记为自然业务主键。虽然数据库主键还是自增id但业务上经常要按手机号查会员。用session.byNaturalId(MemberInfo.class).using(mobile, 13800000000).load()就可以按自然键直接命中而且 Hibernate 内部会缓存这个映射关系效率并不差。第三Formula让orderCount在每次加载实体时用子查询动态算出来。这个字段不写库只作为只读属性存在。适合低频统计字段比如会员详情页面显示订单数。如果这个数字要经常被列表页循环查询那就要好好评估性能不能无脑用。4.3 老教程里的Type用法在 Hibernate 6 里少碰网上能看到大量旧教程在实体里这么写Type(type org.hibernate.type.StringType) private String remark;这个写法在 Hibernate 6 上很容易出问题。因为 Hibernate 6 把类型系统重构了一遍原来五花八门的字符串类型风格被重新整理了。真要是自定义字段映射在 Hibernate 6 里应该优先考虑JdbcTypeCode或者干脆自定义实现UserType按官方文档来。看到Type(type org.hibernate.type.StringType)这种代码直接删掉绝大多数情况下默认映射已经够用了。这个坑特别典型一个项目从 Hibernate 5 升级到 6启动报了一堆UnsupportedTypeException排查到最后就是旧Type写法引发的。5. 事务边界与 Session 生命周期原生 API 的生死必修课5.1 openSession 和 getCurrentSession别选错Session 有两种获取方式很多初学 Hibernate 的人分不清我把它们放在一起对照。获取方式Session 生命周期事务关系使用场景sessionFactory.openSession()每次新建需要手动 close自己手动 begin、commit、rollback批量任务、临时数据处理、非 Spring 环境sessionFactory.getCurrentSession()绑定当前事务由 Spring 事务管理器负责关闭跟随 Transactional 自动提交或回滚业务 Service 方法内 CRUD在 Spring 环境下绝大多数情况应该使用getCurrentSession()。它拿到的是和当前事务绑定的同一个 Session后续第二次、第三次调用拿的都是同一个不会因为开了新 Session 导致一级缓存失效。openSession()用手动管理 Session 非常容易出泄漏问题。有一次我在一个批处理服务里图方便每次循环openSession()忘了写 finally close跑了三个小时后连接池直接打满整个系统报 cannot borrow JDBC connection 卡死。这种惨痛教训希望你们不要重复。5.2 一个规范业务层写法Service 代码应该长成这样。package com.demo.service; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.demo.entity.UserInfo; Service public class UserInfoService { Autowired private SessionFactory sessionFactory; Transactional public Long createUser(String username, String email) { Session session sessionFactory.getCurrentSession(); UserInfo user new UserInfo(); user.setUsername(username); user.setEmail(email); session.persist(user); return user.getId(); } Transactional(readOnly true) public UserInfo getById(Long id) { Session session sessionFactory.getCurrentSession(); return session.get(UserInfo.class, id); } Transactional public void updateEmail(Long id, String newEmail) { Session session sessionFactory.getCurrentSession(); UserInfo user session.get(UserInfo.class, id); user.setEmail(newEmail); } }createUser方法里session.persist(user)之后我没有手动flush或者commit因为事务在Transactional注解声明的方法返回后Spring 会把事务提交Hibernate 会自动把持久化上下文里的变化同步到数据库。updateEmail同理我连update方法都没调用只是改了实体对象的一个 setter事务结束时脏检查就会自动发出 UPDATE 语句。这就是 Hibernate 的脏检查机制在不调saveOrUpdate的情况下照样能自动更新。5.3 不要在大事务里跑一万条循环最常见的性能事故是有人把大批量循环操作包在同一个Transactional方法里。比如循环一万条用户数据逐个更新Session 的一级缓存会一直增长到后来整个应用内存膨胀。正确做法是分批提交并定期清理 Session 缓存。package com.demo.service; import java.util.List; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; Service public class BatchProcessService { Autowired private SessionFactory sessionFactory; public void batchUpdate(ListLong ids) { int batchSize 200; for (int i 0; i ids.size(); i batchSize) { ListLong subIds ids.subList(i, Math.min(i batchSize, ids.size())); doUpdate(subIds); } } Transactional(propagation Propagation.REQUIRES_NEW) public void doUpdate(ListLong subIds) { Session session sessionFactory.getCurrentSession(); for (int i 0; i subIds.size(); i) { UserInfo user session.get(UserInfo.class, subIds.get(i)); user.setEmail(new- i example.com); if (i % 50 0) { session.flush(); session.clear(); } } } }REQUIRES_NEW让每一批次事务独立提交即使某一次失败也不会把前面批次一锅端。flush强制把缓存里的变更同步到数据库clear清空一级缓存避免内存持续占用。参数大小要看单批数据量和数据库承受能力一般 200 到 500 之间的批次比较稳。6. 实际碰到的几个“非 JPA”高频坑附带完整排查链路6.1 No current session context configured这个问题出现频率最高的场景是一个方法明明没有加Transactional却在方法里直接调了sessionFactory.getCurrentSession()。Spring 环境里getCurrentSession()依赖事务同步器没有事务时它不知道要给你哪个 session就会抛HibernateException: No current session context configured。修复方案有两个。第一个是补上Transactional在意读取动作加Transactional(readOnly true)在意写操作加Transactional。第二个是确认hibernate.current_session_context_class没有被改成thread。原生 Hibernate 的 Spring 集成理论上不需要自己设置这个属性如果配置文件里顺手写了thread会直接绕过 Spring 的 Session 绑定逻辑导致事务管理器拿不到当前 Session。6.2 实体在事务外被 JSON 序列化懒加载直接爆掉这是定时炸弹级别的问题。Controller 层返回一个UserInfo实体这个实体里有一个ManyToOne关联属性例如会员的等级对象。当你事务内查到实体并返回后Spring MVC 拿 Jackson 做 JSON 序列化时事务已经关闭了Hibernate 去加载关联对象时发现 Session 没了抛LazyInitializationException: could not initialize proxy - no Session。有些人会想到把spring.jpa.open-in-view设为 true。这个玩意在 Spring Data JPA 项目里默认是 true但我们的方案根本没走 JPA 自动配置这个配置项管不到我们。就算以后切回到 JPA我也建议保持默认关闭因为 OSIV 会把数据库连接一直绑定到视图渲染结束高并发下连接池压力山大。正确的修复方式是在事务方法内部就把关联数据打好返回一个 DTO 对象而不是直接返回实体。比如查询会员详情时在Transactional(readOnly true)方法里把member.getLevel().getLevelName()取值放到一个MemberDetailVO里事务结束后 DTO 已经是纯内存对象随便序列化都不会炸。6.3 多个数据源情况下方言别让 Hibernate 猜如果工程里有多个DataSourcehibernate.dialect一定要显式指定。Hibernate 6 会自动探测数据库类型但在多数据源并存或者某些特殊驱动场景下自动探测会拿到错误的方言。比如有一个 MySQL 数据源一个 PostgreSQL 数据源在同一个应用里切换时让 Hibernate 猜方言的结果就是 SQL 生成风格一把梭要么语法错误要么类型转换异常。配置方法很简单刚才的hibernateProperties()里已经有hibernate.dialect了逐数据源创建LocalSessionFactoryBean时给不同的属性值。老教程里的MySQL5Dialect之类的类名在 Hibernate 6 里已经不存在了统一用包名不带版本号的org.hibernate.dialect.MySQLDialectPostgreSQL 用org.hibernate.dialect.PostgreSQLDialect。6.4 Hibernate 6 的 API 变化老代码别直接抄这些年帮人排查过很多 Hibernate 从 5 升 6 的问题顺手列几个差异点。老代码的session.saveOrUpdate在 Hibernate 6 里不建议继续当默认 API。新增用persist更新用merge语义更接近 JPA 标准也不踩“明明想 insert 结果执行了 update”的雷。老代码的session.createCriteria完全不用。Hibernate 6 里 Criteria 查询已经改成 JPA 风格原生 SQL 想映射实体用session.createNativeQuery(select * from user_info where id ?, UserInfo.class)更直接。老代码的setResultTransformer在 Hibernate 6 已经标废弃。如果你之前靠它把Object[]转成 DTO现在要么改用addScalar加映射要么直接标 DTO 构造器。6.5 启动后表没建出来先看 hbm2ddl 和作用包实体扫描没问题、SessionFactory 也打了日志但数据库里就是没有表。这个问题的排查链路非常固定先看hibernate.hbm2ddl.auto是不是update或者create再看setPackagesToScan的包路径和实体所在包是否一致。如果包路径写成了com.demo.entity但实体类放在com.demo.model扫描结果必然为空。还有一个我常踩的坑Hibernate 6 的hbm2ddl.autoupdate对新加字段的表如果字段类型映射和数据库已有的列类型不兼容不会像 create 那样直接重建而是会静默跳过看起来就像“表没建出来”。开发阶段可以直接用create每次启动都重建表确认映射没问题后再切update。我在实际项目里的习惯是启动时先用CommandLineRunner打印两个东西实体列表和方言类名。实体列表确认扫描成功方言类名确认 Hibernate 没猜错库类型。两个日志都对数据表还不对那就去数据库里看权限是不是给连接用户没有CREATE TABLE权限。这个低级问题我在一个测试环境里排查了大半个小时才想起来。7. 最后补一句我自己的封装习惯原生 Hibernate 用久了你就会发现如果不做一层封装每个 Service 里都会出现“注入 SessionFactory 调 getCurrentSession”的样板代码。我喜欢的做法是做一个极简泛型 DAO封装findById、findAll、persist、merge这几个通用操作业务特定查询还是直接写 HQL 放到各业务 DAO。这套方案的体验接近 Spring Data JPA 的轻量又保持了对 Hibernate 底层 API 的完全可见性。如果你只打算做 CRUD还是那句话直接用 Spring Data JPA 更香。但如果你确实需要 Hibernate 原生注解能力、需要在旧系统里平稳过渡、要把事务和 Session 完全掌控在手里非 JPA 这条路线值得一试。配置上最大的门槛就是依赖选择、自动配置排除、LocalSessionFactoryBean 声明这三步走通后面基本就是实体映射和事务管理的常规操作了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑