You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
    }
}
问询问题
  1. 为何该问题仅出现在AWS EKS环境而非本地?
  2. 升级后的环境中Hibernate 6.x与MySQL TINYBLOB是否存在兼容性问题?另外RDS表已有正确的Schema数据,代码中未执行DDL,为何Spring会隐式执行?
  3. 如何解决该错误?应修改列类型还是调整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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:25:55