Spring Boot初始化Bug:序列化实体关联内嵌类致启动停滞
Spring Boot + JPA 数据库表创建死锁问题:嵌套内嵌类关联实体导致启动失败
发现Spring Boot与JPA在创建数据库表时存在异常:当实体关联序列化内嵌类,该内嵌类又关联另一个序列化内嵌类,最终这个内嵌类再关联回实体时,Spring Boot服务器启动失败,JPA无法创建数据库表,进程卡在HikariPool-1: Start Completed。
已与Spring Boot团队成员沟通,对方判断该问题可能涉及Spring Data JPA、Hibernate或Hikari,建议进一步排查。
问题似乎出在实体与多层嵌套内嵌类之间的序列化关联结构上,可通过以下复现代码验证问题归属。
环境版本要求
- Spring版本:3.2.0及以下
- MySQL版本:8.0.32及以上
- Java版本:18及以上
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.0</version> <relativePath /> <!-- lookup parent from repository --> </parent> <groupId>com.test</groupId> <artifactId>test-api</artifactId> <version>1.0</version> <name>test-backend</name> <description>Test</description> <properties> <java.version>18</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.32</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties
# MySQL database configuration spring.jpa.hibernate.ddl-auto=update spring.datasource.url=jdbc:mysql://localhost:3306/bug_db?allowPublicKeyRetrieval=true&createDatabaseIfNotExist=true&useTimezone=true&serverTimezone=UTC&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.show-sql=true
docker-compose.yml
version: '3.1' services: db: image: mysql:8.0.32 restart: always environment: MYSQL_DATABASE: bug_db MYSQL_ROOT_PASSWORD: root ports: - "3306:3306"
Account.java
@Entity @Table(name = "accounts", uniqueConstraints = { @UniqueConstraint(columnNames = { "username" }) }) public final class Account implements UserDetails { /** The serialVersionUID. */ private static final long serialVersionUID = 221625420706334299L; /** The unique identifier for the account. */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** The user name for authentication. */ @Column(nullable = false, unique = true) @NotBlank(message = "The username cannot be blank") private String username; /** * The password for authentication. */ @Column(name = "password", nullable = false) @JsonIgnore @NotBlank(message = "The password cannot be blank") private String password; /** The information of the account holder. */ @Embedded @Valid private AccountHolderInformation holderInformation; /** Indicates whether it is account non expired. False by default. */ @Column(columnDefinition = "boolean default false", nullable = false) private boolean isAccountNonExpired; /** Indicates whether it is account non locked. False by default. */ @Column(columnDefinition = "boolean default false", nullable = false) private boolean isAccountNonLocked; /** Indicates whether it is enabled. False by default. */ @Column(columnDefinition = "boolean default false", nullable = false) private boolean isEnabled; /** The role of the account in the system. */ @Column(name = "role", nullable = false) @Enumerated(EnumType.STRING) private Role role; public Long getId() { return id; } public void setId(Long id) { this.id = id; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public AccountHolderInformation getHolderInformation() { return holderInformation; } public void setHolderInformation(AccountHolderInformation holderInformation) { this.holderInformation = holderInformation; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { if (this.role == Role.ROLE_ADMIN) { return List.of(new SimpleGrantedAuthority("ROLE_ADMIN"), new SimpleGrantedAuthority("ROLE_USER")); } else { return List.of(new SimpleGrantedAuthority("ROLE_USER")); } } @Override public boolean isAccountNonExpired() { return isAccountNonExpired; } public void setAccountNonExpired(boolean isAccountNonExpired) { this.isAccountNonExpired = isAccountNonExpired; } @Override public boolean isAccountNonLocked() { return isAccountNonLocked; } public void setAccountNonLocked(boolean isAccountNonLocked) { this.isAccountNonLocked = isAccountNonLocked; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return isEnabled; } public void setEnabled(boolean isEnabled) { this.isEnabled = isEnabled; } }
AccountRepository.java
public interface AccountRepository extends JpaRepository<Account, Long>{ Optional<Account> findByUsername(String username); }
Role.java
public enum Role { ROLE_ADMIN("admin"), ROLE_USER("user"); private final String key; private Role(String key) { this.key = key; } public String getRole() { return key; } }
AccountHolderInformation.java
@Embeddable public final class AccountHolderInformation implements Serializable { /** * The serialVersionUID. */ private static final long serialVersionUID = 4089056018657825205L; /** The first name of the account holder. */ @Column(nullable = false) @NotBlank(message = "The name cannot be blank") private String name; /** (Optional) The last name or surname of the account holder. */ @Column @Length private String surname; /** The security information of the account holder. */ @Embedded @Valid private AccountHolderSecurityInformation securityInformation; //getters and setters }
AccountHolderSecurityInformation.java
@Embeddable public final class AccountHolderSecurityInformation implements Serializable { /** * The serialVersionUID. */ private static final long serialVersionUID = 3585858950258340583L; /** The first security question to confirm the identity of an account holder. */ @JsonIgnore @OneToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, mappedBy = "account") private AccountSecurityQuestion securityQuestionOne; //getters and setters }
AccountSecurityQuestion.java
@Entity @Table(name = "accounts_security_questions") public final class AccountSecurityQuestion implements Serializable { /** The serialVersionUID. */ private static final long serialVersionUID = -8188615055579913942L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @JoinColumn(name = "account_id", nullable = false) @JsonIgnore @ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, optional = false) private Account account; @ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER, optional = false) @JoinColumn(name = "security_question_id", nullable = false) private SecurityQuestion securityQuestion; @JsonIgnore @NotBlank(message = "The answer cannot be blank") private String answer; //getters and setters }
AccountSecurityQuestionRepository.java
public interface AccountSecurityQuestionRepository extends JpaRepository<AccountSecurityQuestion, Long> { }
SecurityQuestion.java
@Entity @Table(name = "security_questions") public final class SecurityQuestion implements Serializable { /** The serialVersionUID. */ private static final long serialVersionUID = -6788149456783476682L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false) @NotBlank(message = "The question cannot be blank") private String question; //getters and setters }
SecurityQuestionRepository.java
public interface SecurityQuestionRepository extends JpaRepository<SecurityQuestion, Long> { }
已尝试多种排查手段:清理Maven仓库、本地直接运行、Docker容器部署、删除重建数据库、重命名数据库等。仅在移除某一层内嵌类之间的关联后,启动失败问题才得以解决。
内容的提问来源于stack exchange,提问作者AlefMemTav
相关产品推荐
相关产品推荐

