Reputation: 2116
I am using liquibase as a migration tool for my current project.
The problem is that I am trying to use custom sequence for auto generation ids for entities but something wrong happens.
I've defined an entity table and custom sequence as a similar below:
<databaseChangeLog>
<changeSet id="create_orders_table" author="I am">
<createTable tableName="ORDERS">
<column name="id" type="SERIAL" valueComputed="SEQ_ORDERS.NEXTVAL" valueSequenceCurrent="SEQ_ORDERS.CURRENT" valueSequenceNext="SEQ_ORDERS.NEXTVAL" defaultValueSequenceNext="SEQ_ORDERS.NEXTVAL">
<constraints primaryKey="true" unique="true"/>
</column>
<column name="number" type="VARCHAR(64)"/>
...
</createTable>
</changeSet>
<changeSet id="add_sequence" author="I am">
<createSequence sequenceName="SEQ_ORDERS" cycle="false" minValue="1" maxValue="9223372036854775807" startValue="1" incrementBy="1"/>
</changeSet>
</databaseChangeLog>
But when this migration is applied I see the following table structure in postgresql:
CREATE TABLE public.orders
(
id integer NOT NULL DEFAULT nextval('orders_id_seq'::regclass),
"number" character varying(64),
...
CONSTRAINT pk_orders PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
First unpredictable thing for me here is why liquibase wrote its own orders_id_seq
instead of my SEQ_ORDERS
?
Next I wrote a typical JPA code and it's test using Spring:
@Entity(name = "ORDERS")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ORDERS_ID_GEN")
@SequenceGenerator(name = "ORDERS_ID_GEN", sequenceName = "SEQ_ORDERS")
private long id;
private String number;
//... getters,setters and other stuff
}
public interface OrderRepository extends JpaRepository<Order,Long> {}
Test
@RunWith(SpringRunner.class)
@DataJpaTest
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Rollback(value = false)
public class OrderRepositoryTest {
@Autowired
private TestEntityManager testEntityManager;
@Autowired
private OrderRepository orderRepository;
@Test
public void testThatIdsOfOrdersGenerateCorrectly(){
Order orderOne = new Order();
orderOne.setNumber("order-n-01");
Order orderTwo = new Order();
orderTwo.setNumber("order-n-02");
Order orderThree = new Order();
orderThree.setNumber("order-n-03");
testEntityManager.persist(orderOne);
testEntityManager.persist(orderTwo);
testEntityManager.persist(orderThree);
Assert.assertThat(orderRepository.findOne(new Long(1)),is(orderOne));
Assert.assertThat(orderRepository.findOne(new Long(2)),is(orderTwo));
Assert.assertThat(orderRepository.findOne(new Long(3)),is(orderThree));
}
}
In test logs I see:
Hibernate: select nextval ('seq_orders')
...
java.lang.AssertionError:
Expected: is <Order(id=50, number=order-n-01, ...>
but: was null
After Test is finished In DB I saw three orders with id=50,51,52
First time I thought that orders_id_seq
increments value on 50 each time.
I was very surpised when I saw unmodified orders_id_seq
after test (current value=1) but my SEQ_ORDERS was incremented on 1.
When I run the test once again I got next three orders with ids 100,101,102
Could someone explain me what the hell is going on here? And how to make liquibase&postgresql do right things?
I am using: PostgreSQL 9.6(org.postgresql.9.4.1212 JDBC Driver), Liquibase 3.5.3, Spring Boot 1.5.2
Spring Boot Test Config:
spring.jpa.hibernate.ddl-auto=none
spring.jpa.generate-ddl=false
spring.jpa.database=postgresql
spring.datasource.initialize=false
spring.datasource.username=postgres
spring.datasource.password=********
spring.datasource.url=jdbc:postgresql://localhost:5432/ORDERS_TEST
spring.datasource.driver-class-name=org.postgresql.Driver
liquibase.change-log=classpath:/db/changelog/changelog-master.xml
Upvotes: 2
Views: 10288
Reputation:
If you specify serial
as the data type you should not specify any default value, because Postgres will already do that for you. The sequence and orders_id_seq
and the default value is created by Postgres automatically when you use serial
it is not created by Liquibase.
So if you want to use your own sequence (for whatever reason), specify the column as integer
and specify a defaultValueComputed
using nextval()
. You also need to create the sequence before you create the table.
Also the syntax for getting the next value is not sequence.nextval
but nextval('sequence')
in Postgres:
<databaseChangeLog>
<changeSet id="add_sequence" author="I am">
<createSequence sequenceName="SEQ_ORDERS" cycle="false" minValue="1" maxValue="9223372036854775807" startValue="1" incrementBy="1"/>
</changeSet>
<changeSet id="create_orders_table" author="I am">
<createTable tableName="ORDERS">
<column name="id" type="integer" defaultValueComputed="nextval('seq_orders')>
<constraints primaryKey="true" unique="true"/>
</column>
<column name="number" type="VARCHAR(64)"/>
...
</createTable>
</changeSet>
</databaseChangeLog>
Upvotes: 5