48

So our project use PostgreSQL database and we use JPA for operating the database. We have created the entities from the database with automatic creator in Netbeans 7.1.2.

After small changes our primary key values are described as:

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Basic(optional = false)
@NotNull
@Column(name = "idwebuser", nullable = false)
private Integer idwebuser;

The problem is that now the application is not flexible, because when we modify the database directly (using SQL or another tool) instead of going thru the Java app - the Generated Value is lower than actual database ID value - and so we get error during the creation of new entities.

Is there a possibility that the JPA could just let the database generate the ID automatically and then obtain it after the creation process? Or what could be a better solution? Thanks.

EDIT More specifically: We have a table of users and my problem is that using any type of strategy generationtype, the JPA is inserting a new entity with a specified by it's generator id. Which is wrong for me, because if I make changes to the table on my own, by adding new entries, the GeneratedValue for application is lower than the current ID - what leads us to exception with duplicated ID. Can we fix it ;)?

a short note on the answer There was a little lie from my side because we've used a PG Admin -> View first 100 Rows and edited rows from there instead of using select. ANYWAY, it turns out that this editor somehow skips the process of updating the ID and so even in DB when we write a proper INSERT it is EXECUTED with improper ID! So it was basically more a problem of the editor we used than the database and application...

now it even works using @GeneratedValue(strategy=GenerationType.IDENTITY)

Atais
  • 10,857
  • 6
  • 71
  • 111
  • Does this mean that JPA is not using the PostgreSQL sequence? What is the definition of that column in the table? Is that a `serial` or just an `integer –  Aug 06 '12 at 11:59
  • We are using serial field type. I will describe the problem more in the main question, @a_horse_with_no_name – Atais Aug 06 '12 at 12:01
  • So it does mean JPA is **not** using the associated sequence? That is strange. –  Aug 06 '12 at 12:07
  • Well during tests, we now have about 17 rows, and we use: `@SequenceGenerator(name="pk_sequence",sequenceName="entity_id_seq") @GeneratedValue(strategy=GenerationType.SEQUENCE,generator="pk_sequence")` And we just got an error `Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.2.0.v20110202-r8913): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "webuser_idwebuser_pk" Detail: Key (idwebuser)=(14) already exists.` So it seems something is still wrong. – Atais Aug 06 '12 at 12:20
  • There is a clash in the sequence usage. Apparently not all inserts are using the same strategy on how to generate the PK. –  Aug 06 '12 at 12:23
  • @a_horse_with_no_name, Well that I would tell I know - because when I write a simple query `INSERT (xx,xxx) INTO WEBUSER` (just a sample); I do not specify any Generator. So does it mean I cannot operate on the database itself, when it is used by the application? – Atais Aug 06 '12 at 12:25
  • of course you can, you just need to tell JPA/Hibernate/... to use the same strategy. I believe Craig's Answer shows you everything you need. –  Aug 06 '12 at 12:31
  • Sometimes if you imported data or somehow manually entered id values, your database sequence may not be up to date. It would generate an already existing id. In that case, consider updating your table sequence: SELECT setval('users_id_seq', (select (max(id) + 1) from users)); – Bhdr Dec 12 '22 at 14:45

5 Answers5

95

Given the table definition:

CREATE TABLE webuser(
    idwebuser SERIAL PRIMARY KEY,
    ...
)

Use the mapping:

@Entity
@Table(name="webuser")
class Webuser {

    @Id
    @SequenceGenerator(name="webuser_idwebuser_seq",
                       sequenceName="webuser_idwebuser_seq",
                       allocationSize=1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE,
                    generator="webuser_idwebuser_seq")
    @Column(name = "idwebuser", updatable=false)
    private Integer id;

    // ....

}

The naming tablename_columname_seq is the PostgreSQL default sequence naming for SERIAL and I recommend that you stick to it.

The allocationSize=1 is important if you need Hibernate to co-operate with other clients to the database.

Note that this sequence will have "gaps" in it if transactions roll back. Transactions can roll back for all sorts of reasons. Your application should be designed to cope with this.

  • Never assume that for any id n there is an id n-1 or n+1
  • Never assume that the id n was added or committed before an id less than n or after an id greater than n. If you're really careful with how you use sequences you can do this, but you should never try; record a timestamp in your table instead.
  • Never add to or subtract from an ID. Compare them for equality and nothing else.

See the PostgreSQL documentation for sequences and the serial data types.

They explain that the table definition above is basically a shortcut for:

CREATE SEQUENCE idwebuser_id_seq;
CREATE TABLE webuser(
    idwebuser integer primary key default nextval('idwebuser_id_seq'),
    ...
)
ALTER SEQUENCE idwebuser_id_seq OWNED BY webuser.idwebuser;

... which should help explain why we have added a @SequenceGenerator annotation to describe the sequence.


If you really must have a gap-less sequence (for example, cheque or invoice numbering) see gapless sequences but seriously, avoid this design, and never use it for a primary key.


Note: If your table definition looks like this instead:

CREATE TABLE webuser(
    idwebuser integer primary key,
    ...
)

and you're inserting into it using the (unsafe, do not use):

INSERT INTO webuser(idwebuser, ...) VALUES ( 
    (SELECT max(idwebuser) FROM webuser)+1, ...
);

or (unsafe, never do this):

INSERT INTO webuser(idwebuser, ...) VALUES ( 
    (SELECT count(idwebuser) FROM webuser), ...
);

then you're doing it wrong and should switch to a sequence (as shown above) or to a correct gapless sequence implementation using a locked counter table (again, see above and see "gapless sequence postgresql" in Google). Both the above do the wrong thing if there's ever more than one connection working on the database.

Community
  • 1
  • 1
Craig Ringer
  • 307,061
  • 76
  • 688
  • 778
  • I dont know why or how but it's still not working; we obviously use `CREATE TABLE webuser( idwebuser SERIAL PRIMARY KEY, ... )` In entity I've edited it with sequence annotation as given by your example. Still: when I launch an application and register new user - it inserted him with no problem; then I added 3 dummy rows with following ids with PGAdmin and tried to register next user, which result in error: `ERROR: duplicate key value violates unique constraint "webuser_idwebuser_pk" Detail: Key (idwebuser)=(20) already exists.` I don't really care about gaps either, @Craig. – Atais Aug 06 '12 at 12:47
  • @Atais All I can tell you at this point is that it works fine for me and is required to work by the spec and PostgreSQL documentation. We need to work out what's different about your setup. Maybe you should edit your answer to show your updated mapping and to show the exact text of the insert queries you used in PgAdmin-III. Consider also enabling `log_statement = 'all'` in `postgresql.conf`, restarting Pg, and examining the logs to see what statements Hibernate is actually running, or enabling `hibernate.show_sql=true` in `persistence.xml`. – Craig Ringer Aug 06 '12 at 12:55
  • OMG I have just figure it out! There was a little lie from my side because we've used a PG Admin -> View first 100 Rows and edited rows from there instead of using select. ANYWAY, it turns out that this editor somehow skips the process of updating the ID and so even in DB when we write a proper INSERT it is EXECUTED with improper ID! So it was basically more a problem of the editor we used than the database and application... Thanks anyway! – Atais Aug 06 '12 at 13:01
  • @Atais Glad to know it's resolved. Hopefully you've learned some debugging techniques for next time, too. Trust me, there will be an inexplicable what-the-heck-is-happening next time when working with JPA... – Craig Ringer Aug 06 '12 at 13:03
  • 12
    Just for future googling people: now it even works using `@GeneratedValue(strategy=GenerationType.IDENTITY)` – Atais Aug 06 '12 at 13:06
  • 1
    @Atais It works on EclipseLink, but last I checked (admittedly a while ago) it didn't on Hibernate, I had to use an explicit sequence. The JPA 2.1 spec helpfully says "this specification does not define the exact behavior of these strategies" :-( – Craig Ringer Aug 06 '12 at 13:25
  • https://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Identity_sequencing: Although identity sequencing seems like the easiest method to assign an id, they have several issues. One is that since the id is not assigned by the database until the row is inserted the id cannot be obtained in the object until after commit or after a flush call. Identity sequencing also does not allow for sequence preallocation, so can require a select for each object that is inserted, potentially causing a major performance problem, so in general are not recommended. – jansohn Nov 26 '15 at 08:06
  • @Atais Thank you very much that you found time and posted this line of code which allowed us to save a lot of time. Thank you! – Pavel_K Aug 04 '16 at 07:06
  • Sometimes if you imported data or somehow manually entered id values, your database sequence may not be up to date. It would generate an already existing id. In that case, consider updating your table sequence: SELECT setval('users_id_seq', (select (max(id) + 1) from users)); – Bhdr Dec 12 '22 at 14:45
  • @Bhdr But you shouldn't ever need to do this, it means you did something wrong in the first place. It's ok as a recovery step. But it's also important to understand that the result will still be wrong if there are concurrent inserts. Better to `BEGIN; LOCK TABLE mytable; SELECT setval(...); COMMIT;` – Craig Ringer Dec 21 '22 at 06:10
  • Yes, locking the table can be good. The above approach worked well for me to update the sequence val. – Bhdr Dec 21 '22 at 15:19
2

It seems you have to use the sequence generator like:

@GeneratedValue(generator="YOUR_SEQ",strategy=GenerationType.SEQUENCE)
Denis Zevakhin
  • 626
  • 3
  • 8
  • Can i make Sequence to return a value of current `select count(*) from webuser` instead of the value which would be incremented by the application, @a_horse_with_no_name? – Atais Aug 06 '12 at 12:05
  • 1
    @Atais: **Do not** use count() to generate your IDs. ***Never***. It simply will not work. Use a sequence. If you are worried about gaps in the numbers, then there is something wrong with your PK "design". A sequence is the most efficient, performant, scalable and robust solution to generate unique IDs –  Aug 06 '12 at 12:07
  • @Atais No, and if that's what you want you should edit your answer to say so, because that isn't a database sequence. It sounds like you are attempting to get a gapless sequence. – Craig Ringer Aug 06 '12 at 12:09
  • @a_horse_with_no_name, I've just re-thinked the count() idea, of course it's wrong. Maybe it should return the last-id +1, or something? For clarification please look at question's edit. – Atais Aug 06 '12 at 12:09
  • @Atais: No, don't use last_id + 1 (That **will** be wrong just as well). A sequence ***is*** the most efficient and scalable solution to generate unique IDs. –  Aug 06 '12 at 12:11
  • 1
    @Atais that won't work either. Imagine what happens if two transactions happen at once. You need to use a `SEQUENCE` in the database via a `SequenceGenerator` and accept that the IDs might be more than one apart, there might be gaps of two or three or more IDs. If you *absolutely* *must* have no gaps, see http://stackoverflow.com/questions/2183932/hibernate-annotation-for-postgresql-serial-type/11752742#11752742 but you *should not rely on this*. – Craig Ringer Aug 06 '12 at 12:11
1

Please, try to use GenerationType.TABLE instead of GenerationType.IDENTITY. Database will create separate table which will be use to generate unique primary keys, it will also store last used id number.

Leszek
  • 6,568
  • 3
  • 42
  • 53
  • Just tried, no effect. We have created three first java entity with application, then with queries i've created four more. So now the JPA's sequence is/was stopped on (new) ID=4 but in real it should be (new) ID=8. Table generation type gave us error: – Atais Aug 06 '12 at 09:45
  • Why would you want to use a Hibernate based generator, if the built-in one from the DBMS is much better? (Btw. *Hibernate* will create that table, not the *Database*) –  Aug 06 '12 at 11:57
  • I actually want to use the DMBS but it seems it's using Hibernate's. – Atais Aug 06 '12 at 12:01
  • 1
    `GenerationType.TABLE` is inferior in almost every way to using a `SequenceGenerator` with `GenerationType.SEQUENCE`. I'd only consider using it if I had to have a mapping that was portable to every brain-dead database out there. – Craig Ringer Aug 06 '12 at 12:13
  • @CraigRinger, well this is only a simple application conected with the database, one db one app, nothing more. So i guess it's a bit too complex solution. I've also edited the main question if you may look at it. – Atais Aug 06 '12 at 12:20
  • @Atais Yeah, I saw that. If you want to keep Hibernate edits and edits made from outside Hibernate in sync, use a sequence generator, because that's what PostgreSQL uses for the `SERIAL` columns you're (hopefully) using for your generated primary keys. – Craig Ringer Aug 06 '12 at 12:27
  • I think this is only useful if you want to support multiple databases. – fatihpense Feb 02 '16 at 18:47
1

You can also save yourself some effort by writing a script to perform a mass conversion of the generic GenerationType.IDENTITY to the solution proposed by the selected answer. The below script has some slight dependencies on how the Java source file is formatted and will make modifications without backups. Caveat emptor!

After running the script:

  1. Search and replace import javax.persistence.Table; with import javax.persistence.Table; import javax.persistence.SequenceGenerator;.
  2. Reformat the source code in NetBeans as follows:
    1. Select all the source files to format.
    2. Press Alt+Shift+F
    3. Confirm reformatting.

Save the following script as update-sequences.sh or similar:

#!/bin/bash

# Change this to the directory name (package name) where the entities reside.
PACKAGE=com/domain/project/entities

# Change this to the path where the Java source files are located.
cd src/main/java

for i in $(find $PACKAGE/*.java -type f); do
  # Only process classes that have an IDENTITY sequence.
  if grep "GenerationType.IDENTITY" $i > /dev/null; then
    # Extract the table name line.
    LINE_TABLE_NAME=$(grep -m 1 @Table $i | awk '{print $4;}')
    # Trim the quotes (if present).
    TABLE_NAME=${LINE_TABLE_NAME//\"}
    # Trim the comma (if present).
    TABLE_NAME=${TABLE_NAME//,}

    # Extract the column name line.
    LINE_COLUMN_NAME=$(grep -m 1 -C1 -A3 @Id $i | tail -1)
    COLUMN_NAME=$(echo $LINE_COLUMN_NAME | awk '{print $4;}')
    COLUMN_NAME=${COLUMN_NAME//\"}
    COLUMN_NAME=${COLUMN_NAME//,}

    # PostgreSQL sequence name.
    SEQUENCE_NAME="${TABLE_NAME}_${COLUMN_NAME}_seq"

    LINE_SEQ_GENERATOR="@SequenceGenerator( name = \"$SEQUENCE_NAME\", sequenceName = \"$SEQUENCE_NAME\", allocationSize = 1 )"
    LINE_GENERATED_VAL="@GeneratedValue( strategy = GenerationType.SEQUENCE, generator = \"$SEQUENCE_NAME\" )"
    LINE_COLUMN="@Column( name = \"$COLUMN_NAME\", updatable = false )\n"

    # These will depend on source code formatting.
    DELIM_BEGIN="@GeneratedValue( strategy = GenerationType.IDENTITY )"
    # @Basic( optional = false ) is also replaced.
    DELIM_ENDED="@Column( name = \"$COLUMN_NAME\" )"

    # Replace these lines...
    #
    # $DELIM_BEGIN
    # $DELIM_ENDED
    #
    # With these lines...
    #
    # $LINE_SEQ_GENERATOR
    # $LINE_GENERATED_VAL
    # $LINE_COLUMN

    sed -i -n "/$DELIM_BEGIN/{:a;N;/$DELIM_ENDED/!ba;N;s/.*\n/$LINE_SEQ_GENERATOR\n$LINE_GENERATED_VAL\n$LINE_COLUMN/};p" $i
  else
    echo "Skipping $i ..."
  fi
done

When generating the CRUD application using NetBeans, the ID attributes won't include editable input fields.

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
1

It work for me

  1. create table like this, use SERIAL.
CREATE TABLE webuser(
    idwebuser SERIAL PRIMARY KEY,
    ...
)
  1. add @GeneratedValue(strategy = GenerationType.IDENTITY) at id field.
@Entity
@Table(name="webuser")
class Webuser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    // ....

}
hang gao
  • 421
  • 5
  • 6