Spring Boot升级后AWS EKS环境Hibernate DDL执行错误排查
问题描述
将Spring Boot应用从v2.1.6升级到v3.1.4、Java从8升级到21后,本地(Docker+Minikube)运行正常,但在AWS EKS预发布环境中遇到数据库Schema迁移错误,该错误与Hibernate执行DDL语句相关。
错误日志
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "alter table test_data modify column data tinyblob not null" via JDBC [Data truncation: Data too long for column 'data' at row 1]
关键变更
- Spring Boot从2.1.6升级到3.1.4
- Java从8升级到21
- 本地通过JDBC连接Docker+Minikube中的MySQL
- 部署到AWS EKS环境使用MySQL数据库
环境详情
- 本地环境:Docker、Minikube搭配MySQL 8.0
- AWS EKS预发布环境:AWS EKS搭配AWS RDS的MySQL引擎8.0.35
- 数据库Schema管理:Hibernate自动DDL设置为none
- Java版本:EKS使用openJDK 21,本地使用Oracle JDK 21
- Spring Boot版本:3.1.4
- Hibernate版本:6.2.9
配置片段
1. 预发布环境部署用Dockerfile
FROM --platform=linux/amd64 openjdk:21-jdk-slim ENV SPRING_OUTPUT_ANSI_ENABLED=ALWAYS \ JAVA_OPTS="" WORKDIR /app ENV SPRING_PROFILES_ACTIVE=production COPY target/my-app.jar app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom", "-Duser.timezone=UTC", "-jar","app.jar"]
2. application-staging.properties(相关部分)
spring.datasource.url = jdbc:mysql://localhost:3306/testuser?useUnicode=true&characterEncoding=utf8 spring.datasource.username = root spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
3. 实体定义(简化版)
@Entity @Table(name="test_data") public class TestData { public TestData(){} public TestData(String type, byte[] data, String metadata) { this.type = type; this.data = data; this.metadata = metadata; this.receivedAt = Instant.now(); } @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; private String type; @NotNull @Lob private byte[] data; ... }
4. RDS表的SHOW查询结果
SHOW CREATE TABLE test_data; ===Output of above query below==== CREATE TABLE `test_data` ( `id` bigint NOT NULL AUTO_INCREMENT, `data` mediumblob NOT NULL, `received_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, `metadata` json DEFAULT NULL, `receivedAt` datetime(6) DEFAULT NULL, PRIMARY KEY (`id`), KEY <...> ) ENGINE=InnoDB AUTO_INCREMENT=174604166 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_as_ci
5. application配置文件
spring.config.activate.on-profile = staging spring.jpa.hibernate.ddl-auto=none spring.jpa.generate-ddl=false spring.application.name = testApp spring.datasource.url = jdbc:mysql://<masked> aws.redshift.url=jdbc:redshift://<masked>/ aws.redshift.clusterId=<masked> aws.redshift.dbUser=<masked> aws.redshift.dbPassword=${REDSHIFT_PASSWORD} spring.datasource.username = <masked> spring.flyway.locations=classpath:db/migration spring.data.rest.base-path = repositories # We use multiple thymeleaf configs and none of them are spring MVC spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration #Email Notifications email.password = <masked> jwt.expiryLength.default = <masked> jwt.expiryLength.rememberMe = <masked> jwt.secretKey = <masked> spring.jpa.properties.hibernate.globally_quoted_identifiers=true ## Rabbit Configuration amqp.enabled = true spring.rabbitmq.host = <masked> spring.rabbitmq.port = 5672 spring.rabbitmq.username = <masked> spring.rabbitmq.password=${RABBITMQ_PASSWORD} spring.rabbitmq.listener.simple.concurrency=4 spring.rabbitmq.listener.simple.max-concurrency=8 spring.rabbitmq.listener.simple.retry.initial-interval=20000 spring.rabbitmq.listener.simple.retry.multiplier=2.0 spring.rabbitmq.listener.simple.retry.max-interval=60000 spring.rabbitmq.listener.simple.retry.max-attempts=5 test.agent.docker.registry = <masked> ## Write collected data to connected relational database as blobs datawriter.jpa.enabled = true datawriter.s3.enabled = true # AWS configuration for the various services, like S3, Athena, & Glue aws.region = <masked> aws.accessKey = <masked> aws.secretKey = <masked> aws.kms.arn = <masked> sns.topic.arn.staging = <masked> sns.topic.arn.production = <masked> athena.enabled = true athena.outputlocation = <masked> athena.service.options.clobber = true # Configure S3 locations for data, athena results, etc. s3.data.bucket = <masked> s3.data.environment = <masked> s3.athena.bucket = <masked> # Configure workflows for AWS glue workflows.glue.enabled = true workflows.glue.serviceRole = <masked> workflows.glue.s3Path = <masked> redshift.tmp-dir = <masked> redshift.database = <masked> # TODO -- this shouldn't be in config. Pass it in as env vars or through a secret instead google.api.client_id = <masked> google.api.client_secret = <masked> # eksctl stuff eksctl.enabled=true eksctl.cluster.name=testCluster eksctl.cluster.region=<masked> eksctl.nodegroups.metal.name=metal-nodes eksctl.nodegroups.metal.availZone=<masked> eksctl.nodegroups.metal.capacity=1 # Configure rotating proxy rotating-proxy.hostname=<masked> rotating-proxy.port=<masked> rotating-proxy.shared-dc.username=<masked> rotating-proxy.shared-isp.username=<masked> rotating-proxy.password=<masked> engagement-services.twitter.enabled = false springdoc.swagger-ui.enabled = false ## In-Memory Credentials security.inmemory.user.name = <masked> security.inmemory.user.password = <masked> security.inmemory.user.roles = ADMIN,USER security.cors.allowedOrigins = http://localhost:8080,http://localhost:4200 # Turning off the default health checks for data sources management.health.db.enabled=false # Custom health checks athena.health.check.enabled=true athena.health.check.threshold=20 mysql.health.check.enabled=true # application.properties logging.level.org.springframework.beans.factory=DEBUG #logging.level.org.springframework=DEBUG spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver #spring.datasource.password=${DATASOURCE_PASSWORD} spring.main.allow-circular-references=true
6. SpringDataConfig类
@Configuration @EnableJpaRepositories( basePackages = {"com.org.test.repository"} ) public class SpringDataConfig implements RepositoryRestConfigurer{ @Autowired Environment env; @Bean public SpelAwareProxyProjectionFactory projectionFactory() { return new SpelAwareProxyProjectionFactory(); } @Bean @Primary public LocalContainerEntityManagerFactoryBean entityManagerFactory() { LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean(); emf.setDataSource(springDataDataSource()); emf.setPackagesToScan( new String[]{"com.org.test.domain"}); HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter(); emf.setJpaVendorAdapter(vendorAdapter); HashMap<String, Object> properties = new HashMap<>(); properties.put("hibernate.hbm2ddl.auto", "update"); properties.put("hibernate.dialect", env.getProperty("hibernate.dialect")); properties.put("hibernate.implicit_naming_strategy", "org.hibernate.boot.model.naming.ImplicitNamingStrategyJpaCompliantImpl"); properties.put("hibernate.physical_naming_strategy", "org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl"); emf.setJpaPropertyMap(properties); return emf; } @Bean @Primary @ConfigurationProperties("spring.datasource") public DataSourceProperties dataSourceProperties() { return new DataSourceProperties(); } @Bean("springDataDataSource") @Primary @ConfigurationProperties("spring.datasource") public DataSource springDataDataSource() { return dataSourceProperties().initializeDataSourceBuilder().build(); } @Bean @Primary public JdbcTemplate springDataJdbcTemplate() { return new JdbcTemplate(springDataDataSource()); } @SuppressWarnings("rawtypes") public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { Class[] allEntities = entityManagerFactory().getObject().createEntityManager() .getMetamodel().getEntities().stream() .map(entityType -> ((EntityType<?>) entityType).getJavaType()) .collect(Collectors.toList()) .toArray(new Class[0]); config.exposeIdsFor(allEntities); // By default set the repository rest exposure to off // Only reporitories specified with @RepositoryRestResource will have a REST api exposed config.setRepositoryDetectionStrategy(RepositoryDetectionStrategy.RepositoryDetectionStrategies.ANNOTATED); } @Bean public TaskScheduler taskScheduler() { return new ThreadPoolTaskScheduler(); } }
问询问题
- 为何该问题仅出现在AWS EKS环境而非本地?
- 升级后的环境中Hibernate 6.x与MySQL TINYBLOB是否存在兼容性问题?另外RDS表已有正确的Schema数据,代码中未执行DDL,为何Spring会隐式执行?
- 如何解决该错误?应修改列类型还是调整Hibernate配置?不能重建Schema或丢失数据,有哪些可选方案?
问题解答
1. 为何仅在AWS EKS环境出现问题?
- 本地数据库无历史大数据:本地的
test_data表是Hibernate 6初始化时创建的,默认使用TINYBLOB类型,且未存储超过64KB的数据;而RDS环境的表是旧版本创建的,使用MEDIUMBLOB,且已有超过64KB的记录,Hibernate尝试修改列类型时触发截断错误。 - 方言/驱动差异:RDS MySQL可能有特定配置,导致Hibernate对LOB类型的映射逻辑与本地不同;另外本地可能未显式设置方言,自动检测结果与EKS环境不一致。
2. Hibernate 6.x与TINYBLOB的兼容性及DDL隐式执行原因
- 兼容性问题:Hibernate 6.x对
@Lob注解的处理逻辑变更,Hibernate 5(对应Spring Boot 2.x)中byte[]加@Lob默认映射为MEDIUMBLOB,但Hibernate 6.x默认改为TINYBLOB,导致现有大数据无法适配。 - DDL隐式执行原因:
SpringDataConfig中显式设置了hibernate.hbm2ddl.auto=update,覆盖了配置文件中none的设置。Hibernate启动时对比实体映射与数据库Schema,发现data列类型不一致,就会执行ALTER TABLE语句尝试同步。
3. 解决方案(无数据丢失)
方案一:强制Hibernate映射为MEDIUMBLOB
无需修改数据库,在实体类的data字段上显式指定列类型:
@NotNull @Lob @Column(columnDefinition = "MEDIUMBLOB NOT NULL") private byte[] data;
同时修正SpringDataConfig中的DDL配置:移除properties.put("hibernate.hbm2ddl.auto", "update"),或改为none,让配置文件中的设置生效。
方案二:彻底禁用Hibernate自动DDL
直接在SpringDataConfig中将hibernate.hbm2ddl.auto设置为none,Hibernate将不再执行任何DDL操作。后续Schema变更需通过Flyway等迁移工具处理,避免映射不一致引发其他问题。
方案三:Flyway迁移确认列类型(可选)
编写Flyway迁移脚本,明确确认data列类型为MEDIUMBLOB,确保实体映射与数据库Schema一致,避免Hibernate后续触发修改操作。
内容的提问来源于stack exchange,提问作者Rohan Razdan
相关产品推荐
相关产品推荐

