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
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
.