Search code examples
javaspring-boothibernatejpah2

org.hibernate.mapping.Table.getColumn returns null


I am trying to make junit tests and testing repositories. For that I need to create test database use h2 database, the test class looks:

PokemonRepositoryTest.java:

@DataJpaTest
@AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
@TestPropertySource(properties = {
        "spring.flyway.enabled=false",
        "spring.jpa.hibernate.naming.physical-strategy=com.example.j2.config.H2PhysicalNamingStrategy"
})
public class PokemonRepositoryTest {
    @Autowired
    private PokemonRepository pokemonRepository;

    @Test
    public void saved_pokemon_is_not_null(){
        // arrange
        Pokemon pokemon = Pokemon.builder()
                .name("pikachu")
                .type("electric")
                .build();
        // act
        Pokemon saved = pokemonRepository.save(pokemon);
        // assert
        Assert.notNull(saved,"saved pokemon is null");
        Assert.isTrue(saved.getId() > 0, "saved pokemon is not grater then 0");
    }
}

Now I have a entity called User having physical (table) name as user. However this is not allowed as user (not quoted, thus H2 uppercases it to USER) is reserved keyword. So I decided to implement PhysicalNamingStrategy to prefix all tables created by H2 (again, this is for test purposes, in my development I am using mysql which does not have user as keyword and thus is fine):

H2PhysicalNamingStrategy.java:

@Configuration
public class H2PhysicalNamingStrategy implements PhysicalNamingStrategy {
    private static final String TABLE_PREFIX = "_";
    @Override
    public Identifier toPhysicalCatalogName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return identifier;
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return identifier;
    }

    @Override
    public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return Identifier.toIdentifier(TABLE_PREFIX + identifier.getText());
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return identifier;
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return identifier;
    }
}

Now I am getting error:

...
Hibernate: drop table if exists _pokemon cascade 
Hibernate: drop table if exists _refresh_token cascade 
Hibernate: drop table if exists _review cascade 
Hibernate: drop table if exists _role cascade 
Hibernate: drop table if exists _user cascade 
Hibernate: drop table if exists _user_roles cascade 
Hibernate: create table _pokemon (id bigint generated by default as identity, name varchar(255), type varchar(255), primary key (id))
Hibernate: create table _refresh_token (id bigint generated by default as identity, user_id bigint, token varchar(255), primary key (id))
Hibernate: create table _review (stars integer, id bigint generated by default as identity, pokemon_id bigint, title varchar(255), content clob, primary key (id))
2024-01-07T01:31:07.894+01:00 ERROR 132222 --- [           main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Error creating SQL 'create' commands for table '_role' [Cannot invoke "org.hibernate.mapping.Column.isUnique()" because the return value of "org.hibernate.mapping.Table.getColumn(org.hibernate.mapping.Column)" is null]

It says org.hibernate.mapping.Table.getColumn is null. But why?

EDIT: changed prefix to suffix (as suggested by @Basil Bourque in comments):

H2PhysicalNamingStrategy.java:

@Configuration
public class H2PhysicalNamingStrategy implements PhysicalNamingStrategy {
 private static final String TABLE_SUFFIX = "_";
 ...
 @Override
    public Identifier toPhysicalTableName(Identifier identifier, JdbcEnvironment jdbcEnvironment) {
        return Identifier.toIdentifier(identifier.getText() + TABLE_SUFFIX);
    }
 ...
}

but still the same error:

...
Hibernate: drop table if exists pokemon_ cascade 
Hibernate: drop table if exists refresh_token_ cascade 
Hibernate: drop table if exists review_ cascade 
Hibernate: drop table if exists role_ cascade 
Hibernate: drop table if exists user_ cascade 
Hibernate: drop table if exists user_roles_ cascade 
Hibernate: create table pokemon_ (id bigint generated by default as identity, name varchar(255), type varchar(255), primary key (id))
Hibernate: create table refresh_token_ (id bigint generated by default as identity, user_id bigint, token varchar(255), primary key (id))
Hibernate: create table review_ (stars integer, id bigint generated by default as identity, pokemon_id bigint, title varchar(255), content clob, primary key (id))
2024-01-07T13:36:53.866+01:00 ERROR 3851 --- [           main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.MappingException: Error creating SQL 'create' commands for table 'role_' [Cannot invoke "org.hibernate.mapping.Column.isUnique()" because the return value of "org.hibernate.mapping.Table.getColumn(org.hibernate.mapping.Column)" is null]

EDIT2: here is full source code: github


Solution

  • The error is due to incorrect column name specified in the unique constraint in Role class. The column name in the constraint is roleName, but the actual field name and hence the column name is name. You need to either update the column name in the unique constraint from roleName to name or change the field name from name to roleName.