当序列化实体具有序列化内置类时,Spring Boot 初始化中的错误。 HikariPool-1:开始完成

问题描述 投票:0回答:1

我在创建表时发现了一个与Spring Boot和JPA相关的bug。当有一个序列化实体与一个内置序列化类相关联,而该内置序列化类又拥有另一个内置序列化类,并且最终该类也有一个序列化实体时,Spring Boot 无法启动服务器,JPA 也不会创建数据库表。相反,它会卡在“HikariPool-1:开始完成”。

图片在这里:(https://github.com/spring-projects/spring-boot/assets/97984278/67b8353c-d805-4e94-ab1b-d71ab0b45456

我与 Spring Boot 团队的一名成员交谈(https://github.com/spring-projects/spring-boot/issues/38701),他告诉我这可能是与 spring data jpa、hibernate 相关的错误或 hikari,所以他让我在这里提出一个问题。

问题似乎在于实体与嵌入类和实体的这种串行关联。如果您想运行并检查问题以验证这是否确实是 Spring Boot(因此无法启动)或 JPA 或 Hikari 的问题,我将不胜感激。

  1. Spring 版本:3.2.0 或更低;
  2. MySQL 8.0.32 或更高版本;
  3. 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>

应用程序属性:

# 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"

帐户.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);
}

角色.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> {
}

安全问题.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、删除并重新创建数据库,甚至重命名数据库。奇怪的是,只有当我删除一个嵌入类与另一个嵌入类的关联时,它才起作用。`

spring-boot hibernate spring-data-jpa hikaricp
1个回答
0
投票

我通过删除序列化内置类与序列化实体的关联解决了这个问题,并且它起作用了。

显然,JPA 或 Hibernate 在遇到具有序列化嵌入类的序列化实体时,在启动表创建过程时会遇到困难,而这些嵌入类又涉及序列化实体。结果Spring Boot卡住了,无法启动

© www.soinside.com 2019 - 2024. All rights reserved.