Reputation: 57
We use a dockerized postgres database and have hibernate auto-generate the tables (using spring.jpa.hibernate.ddl-auto: create
) for our integration tests. Using something like H2 is not an option because we do some database-specific operations in a few places, e.g. native SQL queries.
Is there any way to avoid id collisions when all entities use auto-incremented ids? Either by offsetting the start id or, better yet, having all tables use a shared sequence?
Schema is created when the docker container is launched, tables are created by Spring Data JPA/Hibernate
Examples use kotlin syntax and assumes the "allopen"-plugin is applied for entities.
Sometimes we've had bugs where the wrong foreign key was used, e.g. something like this:
@Entity
class EntityOne(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
)
@Entity
class EntityTwo(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
)
@Entity
class JoinEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, columnDefinition = "SERIAL")
var id: Long,
@ManyToOne
@JoinColumn(name = "entity_one_id")
var entityOne: EntityOne,
@ManyToOne
@JoinColumn(name = "entity_two_id")
var entityTwo: EntityTwo,
)
@Repository
interface JoinEntityRepository : JpaRepository<JoinEntity, Long> {
//
// Bug here! Should be "WHERE entityOne.id = :entityOneId"
//
@Query("SELECT entityTwo FROM JoinEntity WHERE entityTwo.id = :entityOneId")
fun findEntityTwoByEntityOneId(entityOneId: Long): Collection<EntityTwo>
}
These bugs can in some circumstances be very hard to find because when the table is created, there may very well be an Entity2 with the same id as some Entity1, and so the query succeeds but the test fails somewhere down the line because while it is returning one or more Entity2, it's not the expected ones.
Even worse, depending on the scope of the test it may pass even if the wrong entity is fetched, or fail only when tests are run in a specific order (due to ids getting "out of sync"). So ideally it should fail to even find an entity when the wrong id is passed. But because the database structure is created from scratch and the ids are auto-incremented they always start at 1.
Upvotes: 1
Views: 683
Reputation: 57
I found a solution to this.
In my resources/application.yml
(in the test folder, you most likely do not want to do this in your main folder) I add spring.datasource.initialization-mode: always
and a file data.sql
.
The contents of data.sql are as follows:
DROP SEQUENCE IF EXISTS test_shared_sequence;
CREATE SEQUENCE test_shared_sequence;
ALTER TABLE entity_one ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');
ALTER TABLE entity_two ALTER COLUMN id SET DEFAULT nextval('test_shared_sequence');
After Spring has auto-generated the tables (using spring.jpa.hibernate.ddl-auto: create
) it will run whatever is in this script, and the script will change all tables to auto-generate ids based on the same sequence, meaning that no two entities will ever have the same id regardless of which table they're stored in, and as such any query that looks in the wrong table for an id will fail consistently.
Upvotes: 1