如何在spring boot中创建H2+flyway测试数据库?

Posted

技术标签:

【中文标题】如何在spring boot中创建H2+flyway测试数据库?【英文标题】:How to create an H2+flyway test database in spring boot? 【发布时间】:2020-12-25 03:10:57 【问题描述】:

我有一个 Spring Boot 项目,我想在其中测试我的控制器。我使用 mysql 数据库进行生产,但想要一个内存数据库来运行测试用例。我使用 Flyway 进行版本控制数据库迁移。我希望我的测试数据库使用相同的版本控制。有人可以帮我解决这个问题吗?

这是我在 src/test/resources 文件夹中的 application.properties 的样子:

# Database Properties
spring.jpa.database=H2
spring.database.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=MySQL;INIT=CREATE SCHEMA IF NOT EXISTS public;DATABASE_TO_UPPER=false

spring.h2.console.enabled=true
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
security.basic.enabled:false

spring.datasource.username:sa
spring.datasource.password:

# Flyway Properties
spring.flyway.locations=filesystem:src/main/resources/db/migration
spring.flyway.enabled=true
spring.flyway.baseline-on-migrate=true

这是我的测试文件:

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;

@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
@ContextConfiguration
public class PermissionsControllerTest 

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void existentUserCanGetTokenAndAuthenticationAndAlsoExtractPermissions() throws Exception 
        String username = "Srishti";
        String body = "" + "\"username\":\"" + username + "\"";

        MvcResult result = mockMvc.perform(MockMvcRequestBuilders.post("/authenticate")
                .contentType(MediaType.APPLICATION_JSON_VALUE).content(body)).andDo(print()).andExpect(status().isOk())
                .andReturn();

        String response = result.getResponse().getContentAsString();

        mockMvc.perform(MockMvcRequestBuilders.get("/permission").header("Authorization", "Bearer " + response))
                .andExpect(status().isOk()).andDo(print()).andReturn();

        mockMvc.perform(MockMvcRequestBuilders.get("/permission/9").header("Authorization", "Bearer " + response))
                .andExpect(status().isOk()).andDo(print()).andReturn();
    

这是我目前得到的输出:

2020-09-06 19:30:54.481  INFO 26936 --- [           main] c.t.L.PermissionsControllerTest          : Starting PermissionsControllerTest on DGKDSQ13 with PID 26936 (started by SrishtiChawla in C:\Users\srishtichawla\Desktop\LCTraining\LCTraining)
2020-09-06 19:30:54.483  INFO 26936 --- [           main] c.t.L.PermissionsControllerTest          : No active profile set, falling back to default profiles: default
2020-09-06 19:30:55.599  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2020-09-06 19:30:55.600  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
2020-09-06 19:30:55.670  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.LOBRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.672  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.PermissionsRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.674  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.ProductCategoryRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.677  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.RolesRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.679  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.ScoringModelSuiteRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.681  INFO 26936 --- [           main] .RepositoryConfigurationExtensionSupport : Spring Data JDBC - Could not safely identify store assignment for repository candidate interface com.training.LCtraining.repository.UserRepository. If you want this repository to be a JDBC repository, consider annotating your entities with one of these annotations: org.springframework.data.relational.core.mapping.Table.
2020-09-06 19:30:55.682  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 74ms. Found 0 JDBC repository interfaces.
2020-09-06 19:30:55.694  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2020-09-06 19:30:55.695  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFERRED mode.
2020-09-06 19:30:55.762  INFO 26936 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 63ms. Found 6 JPA repository interfaces.
2020-09-06 19:30:56.308  INFO 26936 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler@1e5eb20a' of type [org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-06 19:30:56.325  INFO 26936 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'methodSecurityMetadataSource' of type [org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-09-06 19:30:57.018  INFO 26936 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 0 (http)
2020-09-06 19:30:57.032  INFO 26936 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-09-06 19:30:57.033  INFO 26936 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.37]
2020-09-06 19:30:57.216  INFO 26936 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-09-06 19:30:57.216  INFO 26936 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2708 ms
2020-09-06 19:30:57.353  WARN 26936 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2020-09-06 19:30:57.509  INFO 26936 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 6.4.4 by Redgate
2020-09-06 19:30:57.515  INFO 26936 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2020-09-06 19:30:57.746  INFO 26936 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2020-09-06 19:30:57.773  INFO 26936 --- [           main] o.f.c.internal.database.DatabaseFactory  : Database: jdbc:h2:mem:testdb (H2 1.4)
2020-09-06 19:30:57.864  INFO 26936 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 9 migrations (execution time 00:00.036s)
2020-09-06 19:30:57.883  INFO 26936 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2020-09-06 19:30:57.955  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema "PUBLIC": << Empty Schema >>
2020-09-06 19:30:57.970  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.1 - createTables
2020-09-06 19:30:58.100  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.2 - inservalues
2020-09-06 19:30:58.144  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.3 - altertable
2020-09-06 19:30:58.183  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.4 - INSERTVALUES2
2020-09-06 19:30:58.216  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.5 - insertvalues3
2020-09-06 19:30:58.241  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.6 - ALTERTABLE
2020-09-06 19:30:58.264  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.7 - Altertablelob
2020-09-06 19:30:58.293  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.8 - insertvals
2020-09-06 19:30:58.315  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema "PUBLIC" to version 1.10 - uniquekey
2020-09-06 19:30:58.330  INFO 26936 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 9 migrations to schema "PUBLIC" (execution time 00:00.383s)
2020-09-06 19:30:58.541  INFO 26936 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-09-06 19:30:58.639  INFO 26936 --- [         task-1] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2020-09-06 19:30:58.711  INFO 26936 --- [         task-1] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.18.Final
2020-09-06 19:30:58.903  INFO 26936 --- [         task-1] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations 5.1.0.Final
2020-09-06 19:30:59.060  INFO 26936 --- [         task-1] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2020-09-06 19:30:59.320  INFO 26936 --- [           main] o.s.b.a.h2.H2ConsoleAutoConfiguration    : H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem:testdb'
2020-09-06 19:30:59.970  INFO 26936 --- [         task-1] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2020-09-06 19:30:59.982  INFO 26936 --- [         task-1] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2020-09-06 19:31:00.409  INFO 26936 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 2 endpoint(s) beneath base path '/actuator'
2020-09-06 19:31:00.489  INFO 26936 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1c43e84e, org.springframework.security.web.context.SecurityContextPersistenceFilter@5e62ca19, org.springframework.security.web.header.HeaderWriterFilter@493968a9, org.springframework.web.filter.CorsFilter@7bd694a5, org.springframework.security.web.authentication.logout.LogoutFilter@322ab6ce, com.training.LCtraining.filter.JwtFilter@af9dd34, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@6528d339, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@3149409c, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@21ce2e4d, org.springframework.security.web.session.SessionManagementFilter@780c0, org.springframework.security.web.access.ExceptionTranslationFilter@7a8b7e11, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@11ee671f]
2020-09-06 19:31:01.178  INFO 26936 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring TestDispatcherServlet ''
2020-09-06 19:31:01.179  INFO 26936 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Initializing Servlet ''
2020-09-06 19:31:01.195  INFO 26936 --- [           main] o.s.t.web.servlet.TestDispatcherServlet  : Completed initialization in 16 ms
2020-09-06 19:31:01.298  INFO 26936 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 51516 (http) with context path ''
2020-09-06 19:31:01.300  INFO 26936 --- [           main] DeferredRepositoryInitializationListener : Triggering deferred initialization of Spring Data repositories…
2020-09-06 19:31:01.785  INFO 26936 --- [           main] DeferredRepositoryInitializationListener : Spring Data repositories initialized!
2020-09-06 19:31:01.801  INFO 26936 --- [           main] c.t.L.PermissionsControllerTest          : Started PermissionsControllerTest in 7.711 seconds (JVM running for 9.214)
Hibernate: select user0_.user_id as user_id1_9_, user0_.lob_id as lob_id4_9_, user0_.enabled as enabled2_9_, user0_.role_id as role_id5_9_, user0_.username as username3_9_ from user user0_ where user0_.username=?
2020-09-06 19:31:02.278  WARN 26936 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 42102, SQLState: 42S02
2020-09-06 19:31:02.279 ERROR 26936 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Table "user" not found; SQL statement:
select user0_.user_id as user_id1_9_, user0_.lob_id as lob_id4_9_, user0_.enabled as enabled2_9_, user0_.role_id as role_id5_9_, user0_.username as username3_9_ from user user0_ where user0_.username=? [42102-200]

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /authenticate
       Parameters = 
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"22"]
             Body = "username":"Srishti"
    Session Attrs = 

Handler:
             Type = com.training.LCtraining.integration.UserRestController
           Method = com.training.LCtraining.integration.UserRestController#generateToken(AuthRequest)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.dao.InvalidDataAccessResourceUsageException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = "timestamp":"2020-09-06","message":"could not prepare statement; SQL [select user0_.user_id as user_id1_9_, user0_.lob_id as lob_id4_9_, user0_.enabled as enabled2_9_, user0_.role_id as role_id5_9_, user0_.username as username3_9_ from user user0_ where user0_.username=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement","details":"uri=/authenticate"
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

MockHttpServletRequest:
      HTTP Method = POST
      Request URI = /authenticate
       Parameters = 
          Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"22"]
             Body = "username":"Srishti"
    Session Attrs = 

Handler:
             Type = com.training.training.integration.UserRestController
           Method = com.training.training.integration.UserRestController#generateToken(AuthRequest)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.dao.InvalidDataAccessResourceUsageException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 401
    Error message = null
          Headers = [Vary:"Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers", Content-Type:"application/json", X-Content-Type-Options:"nosniff", X-XSS-Protection:"1; mode=block", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json
             Body = "timestamp":"2020-09-06","message":"could not prepare statement; SQL [select user0_.user_id as user_id1_9_, user0_.lob_id as lob_id4_9_, user0_.enabled as enabled2_9_, user0_.role_id as role_id5_9_, user0_.username as username3_9_ from user user0_ where user0_.username=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement","details":"uri=/authenticate"
    Forwarded URL = null
   Redirected URL = null
          Cookies = []
2020-09-06 19:31:02.656  INFO 26936 --- [extShutdownHook] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2020-09-06 19:31:02.659  INFO 26936 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor  : Shutting down ExecutorService 'applicationTaskExecutor'
2020-09-06 19:31:02.659  INFO 26936 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2020-09-06 19:31:02.664  INFO 26936 --- [extShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.

控制器在生产端运行良好。

这是我的 V1.1__createtable.sql 文件

create table permissions(
permissionID int not null auto_increment,
permissionTitle varchar(40) not null,
permissionDescription varchar(255),
PRIMARY KEY (permissionID),
UNIQUE (permissionTitle));


insert into permissions(permissionTitle) values('Create Role');
insert into permissions(permissionTitle) values('Edit Role');
insert into permissions(permissionTitle) values('Delete Role');
insert into permissions(permissionTitle) values('Create User');
insert into permissions(permissionTitle) values('Edit User');
insert into permissions(permissionTitle) values('Delete User');
insert into permissions(permissionTitle) values('Create LOB');
insert into permissions(permissionTitle) values('Edit LOB');
insert into permissions(permissionTitle) values('Delete LOB');

【问题讨论】:

请发布您的V1.1_createTables.sql的内容 @AlexShesterov 我已经添加了 sql 文件,你可以查看一下。 【参考方案1】:

根据日志,Flyway执行成功:

Flyway Community Edition 6.4.4 by Redgate
HikariPool-1 - Starting...
HikariPool-1 - Start completed.
Database: jdbc:h2:mem:testdb (H2 1.4)
Successfully validated 9 migrations (execution time 00:00.036s)
Creating Schema History table "PUBLIC"."flyway_schema_history" ...
Current version of schema "PUBLIC": << Empty Schema >>
Migrating schema "PUBLIC" to version 1.1 - createTables
Migrating schema "PUBLIC" to version 1.2 - inservalues
Migrating schema "PUBLIC" to version 1.3 - altertable
Migrating schema "PUBLIC" to version 1.4 - INSERTVALUES2
Migrating schema "PUBLIC" to version 1.5 - insertvalues3
Migrating schema "PUBLIC" to version 1.6 - ALTERTABLE
Migrating schema "PUBLIC" to version 1.7 - Altertablelob
Migrating schema "PUBLIC" to version 1.8 - insertvals
Migrating schema "PUBLIC" to version 1.10 - uniquekey
Successfully applied 9 migrations to schema "PUBLIC" (execution time 00:00.383s)

可以看到应用了 9 个迁移:createTables、inservalues、altertable、INSERTVALUES2、insertvalues3、ALTERTABLE、Altertablelob、insertvals、uniquekey。

这个列表是完整的还是你期待更多的迁移?

您稍后在日志中看到的错误来自 Hibernate,而不是来自 Flyway。 它说:

Table "user" not found

查看迁移名称,我希望user 表是由createTables 迁移创建的。但是V1.1__createTables.sql 中没有CREATE TABLE 'user' 语句。

请仔细检查您的迁移中是否定义了所有必要的表。你不应该改变旧的迁移。相反,您可以为丢失的表创建新的迁移并使用CREATE TABLE IF NOT EXISTS 语法,以便与现有的生产环境兼容。

【讨论】:

这些都是迁移,我稍后定义了一个表“用户”。我自己解决了这个问题。我只需要将数据源的 url 更改为 jdbc:h2:file:~/mydb。不过不太清楚这有什么不同。谢谢你的帮助。

以上是关于如何在spring boot中创建H2+flyway测试数据库?的主要内容,如果未能解决你的问题,请参考以下文章

使用 H2 数据库自动创建的 Spring Boot

通过 Spring Boot JPA + Postgresql + H2 执行间隔

在 Spring Boot 中创建外键 - H2 数据库

如何在 Spring Boot 中创建自定义错误页面

如何从 JSON 数组在 DB 中创建表以在 Spring Boot 中创建 REST API

spring boot 组件扫描问题