Search code examples
javaspringspring-bootmavenspring-test

Hibernate h2 database context loads tests Spring Boot 3.0.1


I am getting an error I don't know how to resolve when running my basic contextLoads() test in spring boot

my 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.0.1</version>
      <relativePath /> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.vweinert</groupId>
   <artifactId>feddit-backend</artifactId>
   <name>feddit-backend</name>
   <description>backend for feddit</description>
   <properties>
      <java.version>17</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </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-security</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-validation</artifactId>
      </dependency>

      <dependency>
         <groupId>org.springframework</groupId>
         <artifactId>spring-orm</artifactId>
      </dependency>

      <dependency>
         <groupId>org.hibernate.validator</groupId>
         <artifactId>hibernate-validator</artifactId>
      </dependency>

      <dependency>
         <groupId>org.hibernate.orm</groupId>
         <artifactId>hibernate-spatial</artifactId>
         <version>6.1.6.Final</version>
      </dependency>
      <dependency>
         <groupId>org.postgresql</groupId>
         <artifactId>postgresql</artifactId>
         <scope>runtime</scope>
      </dependency>
      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.modelmapper</groupId>
         <artifactId>modelmapper</artifactId>
         <version>3.1.1</version>
      </dependency>
      <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt-api</artifactId>
         <version>0.11.5</version>
      </dependency>
      <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt-impl</artifactId>
         <version>0.11.5</version>     
      </dependency>
      <dependency>
         <groupId>io.jsonwebtoken</groupId>
         <artifactId>jjwt-jackson</artifactId>
         <version>0.11.5</version>
      </dependency>
   </dependencies>
   <packaging>jar</packaging>
   <build>
      <finalName>backend</finalName>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
               <excludes>
                  <exclude>
                     <groupId>org.projectlombok</groupId>
                     <artifactId>lombok</artifactId>
                  </exclude>
               </excludes>
            </configuration>
         </plugin>
         <plugin>
            <groupId>com.diffplug.spotless</groupId>
            <artifactId>spotless-maven-plugin</artifactId>
            <version>2.28.0</version>
            <configuration>
              <!-- optional: limit format enforcement to just the files changed by this feature branch -->
              <ratchetFrom>origin/master</ratchetFrom>
              <formats>
               <!-- you can define as many formats as you want, each is independent -->
               <format>
                 <!-- define the files to apply to -->
                 <includes>
                  <include>*.md</include>
                  <include>.gitignore</include>
                 </includes>
                 <!-- define the steps to apply to those files -->
                 <trimTrailingWhitespace/>
                 <endWithNewline/>
                 <indent>
                  <tabs>true</tabs>
                  <spacesPerTab>4</spacesPerTab>
                 </indent>
               </format>
              </formats>
              <!-- define a language-specific format -->
              <java>
               <!-- no need to specify files, inferred automatically, but you can if you want -->
           
               <!-- apply a specific flavor of google-java-format and reflow long strings -->
               <googleJavaFormat>
                 <version>1.8</version>
                 <style>AOSP</style>
                 <reflowLongStrings>true</reflowLongStrings>
               </googleJavaFormat>
              </java>
            </configuration>
           </plugin>
      </plugins>
   </build>

</project>

My test

package com.vweinert.fedditbackend;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class FedditBackendApplicationTests {

   @Test
   void contextLoads() {
   }

}

The error on mvn test

Caused by: org.springframework.beans.factory.BeanCreationException: 
Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is java.lang.IllegalStateException: PostInitCallback queue could not be processed...
        - PostInitCallbackEntry - Entity(com.vweinert.fedditbackend.entities.User) `sqmMultiTableInsertStrategy` interpretation
        - PostInitCallbackEntry - Entity(com.vweinert.fedditbackend.entities.Comment) `sqmMultiTableInsertStrategy` interpretation
        - PostInitCallbackEntry - Entity(com.vweinert.fedditbackend.entities.Post) `sqmMultiTableInsertStrategy` interpretation

My entities

User.java

package com.vweinert.fedditbackend.entities;

import java.time.LocalDateTime;
import java.util.Set;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Transient;
import jakarta.persistence.FetchType;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

import com.fasterxml.jackson.annotation.JsonInclude;

import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.vweinert.fedditbackend.request.auth.LoginRequest;
import com.vweinert.fedditbackend.request.auth.SignupRequest;

@Entity
@Table(name="users")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable=false,unique = true)
    @NotBlank(groups = {SignupRequest.class}, message = "email is blank")
    @Email(groups = {SignupRequest.class}, message = "invalid email")
    private String email;
    @Column(nullable=false,updatable = false,unique = true)
    @NotBlank(groups = {LoginRequest.class, SignupRequest.class},message = "username is blank")
    @Size(min = 8, max = 32, groups = {LoginRequest.class, SignupRequest.class}, message = "username must be between 8 and 32 characters")
    private String username;
    @Column(nullable=false)
    @NotBlank(groups = {LoginRequest.class, SignupRequest.class},message = "Missing password")
    @Size(min = 8, max = 32, groups = {LoginRequest.class, SignupRequest.class}, message = "password must be between 8 and 32 characters")
    private String password;
    @Column(columnDefinition = "text")
    private String about;
    @Column(nullable=false,updatable = false)
    @CreationTimestamp
    private LocalDateTime createdAt;
    private LocalDateTime passwordChangedAt;
    private LocalDateTime aboutChangedAt;
    @Column(nullable = false)
    @Builder.Default
    private Boolean deleted = false;
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(    name = "user_roles", 
            joinColumns = @JoinColumn(name = "user_id"), 
            inverseJoinColumns = @JoinColumn(name = "role_id"))
    private Set<Role> roles;
  
    @OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
    private Set<Post> posts;

    @OneToMany(mappedBy = "user",fetch = FetchType.LAZY)
    private Set<Comment> comments;
    @Transient
    @JsonInclude()
    private String jwt;
    public User(String username, String email, String password){
        this.username = username;
        this.email = email;
        this.password = password;
    }
}

Comment.java

package com.vweinert.fedditbackend.entities;

import java.time.LocalDateTime;

import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import com.vweinert.fedditbackend.request.comment.PostComment;
import com.vweinert.fedditbackend.request.comment.PutComment;

@Entity
@Table(name="comments")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Comment {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @NotNull(groups = {PutComment.class})
    private Long id;
    @Column(nullable=false,columnDefinition = "text")
    @NotEmpty(groups = {PutComment.class, PostComment.class})
    private String content;
    @Column(nullable=false,updatable = false)
    @CreationTimestamp
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name="post_id", referencedColumnName = "id")
    private Post post;
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name="user_id", referencedColumnName = "id")
    private User user;
    @Column(nullable=false)
    @Builder.Default
    private Boolean deleted = false;
}

Post.java

package com.vweinert.fedditbackend.entities;

import java.time.LocalDateTime;
import java.util.List;

import com.vweinert.fedditbackend.request.post.PostPost;
import com.vweinert.fedditbackend.request.post.PutPost;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;

import jakarta.validation.constraints.NotBlank;
import org.hibernate.annotations.CreationTimestamp;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Table(name="posts", indexes = {@Index(name="on_created_at", columnList = "createdAt DESC")})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Post {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(nullable=false,updatable = false)
    @NotBlank(groups = {PostPost.class},message = "Missing password")
    private String title;
    @Column(nullable=false,columnDefinition = "text")
    @NotBlank(groups = {PutPost.class, PostPost.class},message = "Missing password")
    private String content;
    @Column(nullable=false,updatable = false)
    @CreationTimestamp
    private LocalDateTime createdAt;
    private LocalDateTime modifiedAt;
    @OneToMany(mappedBy = "post")
    private List<Comment> comments;
    @ManyToOne
    @JoinColumn(name="user_id", referencedColumnName = "id")
    private User user;
    @Column(nullable=false)
    @Builder.Default
    private Boolean deleted = false;
}

application.properties in test folder

spring.datasource.url=jdbc:h2:mem:testdb
spring.jpa.hibernate.ddl-auto: update

I am expecting this test to pass. Note the use of the H2 database instead of postgres for my test. I recently updated this project from spring boot 2.7.6 to 3.0.1, so there have been quite a few issues. Not sure what to try to get this basic test to work.

Note when using postgres at regular runtime it works fine.


Solution

  • added spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect

    Now my test works. I observed at spring-boot start that it was using postgres dialect, now it works with h2