Before I added Flyway to my project, I could run POST request and the new user was created successfully with ID = 1, next one ID = 2 etc.
Then I added Flyway to create tables and insert some test data by V1_init.sql:
create table "user"(
id int8 not null,
username varchar(255),
);
insert into "user" values (1, 'user1');
insert into "user" values (2, 'user2');
insert into "user" values (3, 'user3');
Table is created. Users are inserted.
Trying to run POST request -> error 500
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "organisation_pkey" Key (id)=(1) already exists.
So my app should add new user with ID=4 but it looks like it can't recognize that there are 3 users already added.
I'm using GenericEntity:
#Getter
#Setter
#MappedSuperclass
public abstract class GenericEntity<ID extends Serializable> implements Serializable {
#Id
#GeneratedValue
protected ID id;
}
application.properties:
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/my-app
spring.datasource.username=user
spring.datasource.password=user
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
I tried to use all strategies #GeneratedValue, changing spring.jpa.hibernate.ddl-auto, adding users in init.sql without id (not working)
but still no positive effects. Any ideas what could be wrong?
You seem to have only a half understanding of what you're doing...
I tried to use all strategies #GeneratedValue
You don't need to randomly try strategies, you need to pick the one that matches your current database design.
changing spring.jpa.hibernate.ddl-auto
This is dangerous and you should set it to "none", given that you are using flyway.
adding users in init.sql without id (not working)
This will only work if postgresql is set up to automatically generate ids (which is easiest through a sequence).
From your code, it does not look like that is the case.
what could be wrong?
JPA's #GeneratedValue is capable of ensuring that values are generated when it is responsible for creating rows (that means when you pass EntityManager#persist). It does not and can not know about your flyway scripts where you bypass JPA to insert rows manually.
Furthermore, let's look at #GeneratedValue's strategy property. The strategy you choose will influence how JPA generates IDs. There are only a few options: TABLE, SEQUENCE, IDENTITY and AUTO. Since you did not explicitly specify a strategy, you are currently using the default, which is AUTO. This is not recommended because it is not explicit, and now it's hard to say what your code is doing.
Under the TABLE and SEQUENCE strategies, JPA will do an interaction with the database in order to generate an ID value. In those cases, JPA is responsible for generating the value, though it will rely on the database to do so. Unsurprisingly, the former will use a table (this is rare, btw, but also the only strategy that is guaranteed to work on all RDBMS) and the latter will use a sequence (far more common and supported by practically every commercially relevant RDBMS).
With IDENTITY, JPA will not attempt to generate a key at all, because this strategy assumes that the DB will generate an ID value on its own. The responsibility is thus delegated to the database entirely. This is great for databases that have their own auto-increment mechanism.
Postgres does not really have an auto-increment system but it has some nice syntactic sugar that nearly makes it work like it: the serial "datatype". If you specify the datatype of a column as "serial", it will in fact be created with datatype int, but postgresql will also create a sequence and tie the default value of the ID column to the sequence's next value generator.
In your case, JPA is most likely using either SEQUENCE or TABLE. Since your DDL setting is set to "update", Hibernate will have generated a table or sequence behind your back. You should check your database with something like pgAdmin to verify which it is, but I'd put my money on a sequence (so I'm assuming it's using the SEQUENCE strategy).
Because you haven't specified a #SequenceGenerator, a default will be used which, AFAIK, will start from 1.
Then when JPA tries to insert a new row, it will call that sequence to generate an ID value. It will get the next value of the sequence, which will be 1. This will conflict with the IDs you manually entered in flyway.
My recommended solution would be to:
redefine your postgresql data type from int8 to "serial" (which is actually int + a sequence + sets up default value linking the ID column to the sequence so that postgres will automatically generate an ID if you don't explicitly specify one - careful, also don't specify null, just don't specify the ID column in the insert statement at all!)
explicitly set the generator strategy to IDENTITY on the JPA side
update your flyway scripts to insert users without explicit ID value (this will ensure that the test data advance the sequence, so that when JPA uses that same sequence later, it will not generate a conflicting ID)
I'd say there are alternative solutions, but other than using the TABLE strategy or generating keys in memory (both things which you should avoid), there isn't really a viable alternative because it will boil down to using a sequence anyway. I suppose it's possible to manually specify the sequence, forego the default value on the id field, call the sequence manually in your insert statements, and map the sequence explicitly in JPA... but I don't see why you'd make things hard on yourself.
Related
I'm migrating an app to use CockroachDB and we are using the GeneratedValue mapping in Java with SERIAL type columns to manage primary keys.
id SERIAL PRIMARY KEY -- SQL
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY) -- Java JPA
We are getting the following error
ERROR: currval(): relation "scm_supply_centers_id_seq" does not exist
We dug a little and found out that it's because of PostgreSQL Dialect trying to get the last id inserted in as you can see in this link
PostgreSQL81IdentityColumnSupport
How can I find a workaround for this issue?
The SERIAL type is not backed by a sequence in CockroachDB. In order to use a SEQUENCE you need to explicitly create it and use the nextval() function as the DEFAULT value for the column. For example,
CREATE SEQUENCE customer_seq;
CREATE TABLE customer_list (
id INT PRIMARY KEY DEFAULT nextval('customer_seq'),
customer string,
address string
);
Note that there are performance implications to using SEQUENCE vs SERIAL due to the additional synchronization and communication requirements. See https://www.cockroachlabs.com/docs/stable/create-sequence.html for more details.
The above doesn't exactly answer your question, though. I don't know how you would use an explicit sequence from Hibernate.
I'm getting the "No data type for node" error when I run this query:
session.createQuery("select nextval( 'next_num_seq' )")
which I know means that I need to make it a property of a class, but I haven't been able to find a way to add a sequence to a class, only how to make a sequence generate IDs for a class.
Is there a way to include a sequence in a Hibernate mapping that isn't an ID generator?
As such, this question is valid, yet the path to the solution is headed in the wrong direction. Mapping a Sequence into a managed domain entity is not a "good" idea, as these are two separate concepts.
Sequences, such as the one you are trying to query from a PostgreSQL backend, are a central concept for the generation of unique IDs for primary key values of tuples or - from an ORM application perspective - Java objects. Thus, it is not clever to map their current state to a domain entity. Instead, one sets a single, particular value drawn from such a sequence - e.g. next_num_seq - into one particular object to be persisted in a relational database. Therefore, the related class of such an domain object is linked to this sequence by, for instance, dedicated ORM annotations (or via similar approaches).
In the JavaDoc of the Session interface we find the method createNativeQuery(String sql) which is inherited from the EntityManager interface, see also here.
It is described as follows:
Query createNativeQuery(java.lang.String sqlString)
Create an instance of Query for executing a native SQL statement, e.g., for update or delete.
Parameters:
sqlString - a native SQL query string
Returns:
the new query instance
Thus, you could modify your code to execute the native query against your PostgreSQL database as follows:
Query q = session.createNativeQuery("select nextval( 'next_num_seq' )");
This gives you the option to read the next valid sequence value as a long or Number instance for your programming purposes.
Note well: Be careful not to reuse this value multiple times (for several objects), as this might cause consistency trouble in your backend when used, for instance, in the context of separate threads.
Hope this helps.
I have one project with IDs in the database named like FOO_ID (for the table FOO). I find it convenient to define a small class in Java called FooId wrapping the actual value, and map the FOO_ID column's type to FooId in Java. This allows for better type checking, as I can't accidentally write a "foo id" into a "bar id" column without the compiler giving me an error.
On a new project, all the ID columns are simply called ID in every table. I can't change the database schema, that's out of my hands alas. I would love for <forcedType> to be able to match "column named 'ID' in table named 'FOO'" but right now I can't see how to do that, I don't think it's possible.
Is it possible to constrain <forcedType> based on the table name in addition to the column name? If so, what's the syntax?
Thanks in advance!
Existing feature request
This idea has been discussed on the jOOQ mailing list a couple of times and there's also a pending feature request that will add support for a special generated key type: #6124.
In addition to what you suggested, foreign key types should match their referenced primary key types. The challenge with these types is to make it work for all edge cases including:
Composite keys
Unique keys (which can also be referenced by foreign keys in many databases)
Columns participating in several unique keys / foreign keys
While this is certainly an interesting feature, it is not yet available in jOOQ 3.9.
Workaround
You can work around this limitation yourself by:
Extending the JavaGenerator in order to generate your own type per primary key / foreign key
Using <forcedType/> to replace all single-column key types by your own generated types
This is best done by using the programmatic code generation configuration - otherwise you'd be typing quite a bit of XML (of course, you could also use XSLT to generate the config).
Assume that you have a STORE table having a varchar column STATUS that accepts values (OPEN,CLOSED)
On java side, and especially in your sqls I find myself writing queries like this
select * from store where status='OPEN'
Now this is not a written contract and is open to lots of bugs.
I want to manage cases where on db side a new status added or an existing one renamed and handle it on java side. For example if on STORE table if all statuses with OPEN are changed to OP, my sql code will fail.
PS:This question is in fact programming language and database server agnostic, but I tag it with java since I deal with it more.
Your need is a bit strange. Usually stuff don't just "happen" in database, and you don't have to cope with it. Rather, you decide to change things in your app(s) and both change your code and migrate your data.
This being said, if you want to ensure your data are consistent with a well-known set of values, you can create library tables. In your case:
create table STORE (status varchar(32)) -- your table
create table LIB_STORE_STATUS (status varchar(32) unique) -- a lib table for statuses
alter table STORE add constraint FK_STORE_STATUS foreign key (status) references LIB_STORE_STATUS(status) -- constraints the values in your STORE table
Then:
insert into STORE values ('A') -- fails
insert into LIB_STORE_STATUS values ('A')
insert into STORE values ('A') -- passes
With this, you just have to ensure your lib table is always in sync with your code (i.e. your enum names when using JPA's #Enumerated(EnumType.STRING) mapping strategy).
Use enums, you can map directrly to the enum instance name (not necessary to convert to the int ordinal)
But in this case I would have a boolean/bit column called open, and its possible values would be true or false.
(boolean is bit 0/1 in most DB's)
I want to have a column for an entity which only accepts one of an enumerated set of values. For example let's say I have a POJO/entity class "Pet" with a String column "petType". I want petType to only allow one of three values: "cat", "dog", or "gorilla". How would I go about annotating the getPetType() method in order to have a database level constraint created which enforces this?
I am allowing Hibernate to create or update my database table at application start up via the Hibernate property "hbm2ddlauto" being set to "update".
I have tried using a parameterized user type in association with the #Type annotation but this doesn't appear to provide any sort of constraint on the database column itself. There doesn't appear to be a way of specifying this sort of constraint in the #Column annotation short of using some SQL with the columnDefinition element, and I'm hesitant to go this route since it seems that whatever I use there will not be cross platform/database independent (important to me since I run my code in production on Oracle but I do testing locally using HSQLDB and Derby). Maybe what I want to do just can't be done simply using annotations.
Thanks in advance for any insight you can give me on this topic.
Create a enum of type PetType and defined you mapping as
#Enumerated(EnumType.STRING)
That way, strings are stored in the database and your java enum type only accept the 3 values you specify.