Generate "GENERATED ALWAYS AS IDENTITY" With Spring JPA for PostgreSQL - postgresql

Is it possible to get Spring-Data-JPA to generate PRIMARY KEY column with GENERATED ALWAYS AS IDENTITY.
class SomeClass {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private ID id;
...
}
The result obtained with javax.persistence.* library is:
CREATE TABLE IF NOT EXISTS public.sometable
(
id bigint NOT NULL DEFAULT nextval('sometable_id_seq'::regclass),
...
CONSTRAINT sometable_pkey PRIMARY KEY (id)
...
);
However, the ideal code would be:
CREATE TABLE IF NOT EXISTS public.sometable (
id bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY
...
);
Is there a directive to tell Spring Data to generate GENERATED ALWAYS AS IDENTITY with PostgreSQL?

No, it’s not possible. Have tested with columnDefinition, and it either produces an exception or an invalid DDL. Have a nice day :)

Related

Use postgres table sequence instead of sharing hibernate_sequence

When I do anything with a table, it always show the error:
Hibernate: select nextval ('hibernate_sequence')
2019-07-20 16:15:44.877 WARN 58376 --- [nio-9000-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 42P01
2019-07-20 16:15:44.877 ERROR 58376 --- [nio-9000-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: relation "hibernate_sequence" does not exist
I DON'T want to use hibernate_sequence to share id sequence between tables, but want to define id seq for each table and use them respectively.
I use Spring Boot 2.1.6.RELEASE, Spring Data JPA (Hibernate 5.3.10.Final), and Postgres 11.2, and define the id field with BigSerial type and hope to use the id sequence of each table in respective entity class.
The demo repo is here: https://github.com/Redogame/share_hibernate_sequence
Create user table (use identity as table name because user is a Postgres reserved keyword).
By defining id with bigserial type, Postgres will create a identity_id_seq automatically, and I verified that identity_id_seq has been created successfully.
create table identity
(
id bigserial not null
constraint identity_pkey
primary key,
name varchar(255) not null
constraint identity_name_key
unique
constraint identity_name_check
check ((name)::text <> ''::text),
created_date timestamp not null,
created_by_id bigint not null
constraint identity_identity_id_fk
references identity,
last_modified_date timestamp not null,
last_modified_by_id bigint not null
constraint identity_identity_id_fk_2
references identity,
version bigint not null
);
Specify a sequence generator to use this id sequence:
#Table(name = "identity")
public class UserEntity extends Auditable<Long> {
#Id
#SequenceGenerator(name="identity_id_seq", sequenceName = "identity_id_seq", initialValue=1, allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="identity_id_seq")
private Long id;
But it doesn't work. I also tried to config spring.jpa.hibernate.use-new-id-generator-mappings and spring.jpa.properties.hibernate.id.new_generator_mappings, but still not work.
spring:
jpa:
hibernate:
use-new-id-generator-mappings: false
properties:
hibernate:
id:
new_generator_mappings: false
I expect not to use hibernate_sequence, that is: don't execute select nextval ('hibernate_sequence') before/after any SQL statement.
Try the below steps
CREATE SEQUENCE IF NOT EXISTS manual_seq;
Change create table script
create table identity
(
id integer NOT NULL DEFAULT nextval('manual_seq'::regclass),
name varchar(255) not null
constraint identity_name_key
unique
constraint identity_name_check
check ((name)::text <> ''::text),
created_date timestamp not null,
created_by_id bigint not null,
last_modified_date timestamp not null,
last_modified_by_id bigint not null,
version bigint not null,
CONSTRAINT manual_seq_pkey PRIMARY KEY (id)
);
I removed the foreign key constraint for testing purposes.
Update the entity mapping
#Entity
#Table(name = "identity")
#JsonIgnoreProperties(ignoreUnknown = true)
public class UserEntity extends Auditable<Long> {
#Id
#SequenceGenerator(name="manual-seq", sequenceName = "manual_seq",allocationSize = 1)
#GeneratedValue(generator="manual-seq")
private Long id;
#Basic
#Column(name = "name", nullable = false)
private String name;
#MappedSuperclass
#JsonIgnoreProperties({"new", "createdDate", "createdById", "lastModifiedDate", "lastModifiedById", "version"})
abstract class Auditable<PK extends Serializable>{
#NotAudited
#CreatedDate
#Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
#NotAudited
#CreatedBy
private Long createdById;
#LastModifiedDate
#Temporal(TemporalType.TIMESTAMP)
private Date lastModifiedDate;
#LastModifiedBy
private Long lastModifiedById;
#NotAudited
#Version
private Long version;
Revert the spring.jpa.hibernate.use-new-id-generator-mappings
The issue was extending AbstractPersistable because of which database sequence was not getting used. Also, note I have removed the audit for testing purposes.
The same problem was happening to me. I explicitly set spring.jpa.properties.hibernate.id.new_generator_mappings=false but select nextval ('hibernate_sequence') was still run by Hibernate.
I found that when we use the #GeneratedValue annotation without setting a strategy, it defaults to AUTO, which means, Hibernate will try to generate the ID value using hibernate_sequence and then it wil fail because it doesn't exist in the database.
So, I made #GeneratedValue (strategy = GenerationType.IDENTITY) and tried again. In this case, the ID value was generated by my identity column in the database (the primary key that is automatically incremented) and not by hibernate_sequence.
create table users (id serial not null, name varchar(250), primary key (id));

JPA Inheritance: Query fails with unknown column

Executing the following JPA query while using EclipseLink v2.7.4:
SELECT pr FROM AbstractProduct pr WHERE pr.shelve.superMarket.id = :1 ORDER BY pr.sortOrder
Gives the following error:
Unknown column 't0.SORTORDER' in 'order clause'
Error Code: 1054
Call: SELECT t2.ID, t2.SORTORDER, t2.SHELVE_ID FROM APPLE t2, SHELVE t1 WHERE ((t1.SUPERMARKET_ID = ?) AND (t1.ID = t2.SHELVE_ID)) ORDER BY t0.SORTORDER
bind => [12]
The query is refering to t0 but nowhere in the generated query does it define which table t0 is.
These are the entities that I'm using:
#Entity
public class SuperMarket {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "superMarket")
List<Shelve> shelves;
}
#Entity
public class Shelve {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
protected SuperMarket superMarket;
#OneToMany(mappedBy = "shelve")
protected List<AbstractProduct> products;
}
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
protected Shelve shelve;
protected long sortOrder;
}
#Entity
public class Apple extends AbstractProduct {
}
#Entity
public class Banana extends AbstractProduct {
}
Which results in the following queries:
CREATE TABLE SUPERMARKET (ID BIGINT NOT NULL, PRIMARY KEY (ID));
CREATE TABLE SHELVE (ID BIGINT NOT NULL, SUPERMARKET_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE APPLE (ID BIGINT NOT NULL, SORTORDER BIGINT, SHELVE_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE BANANA (ID BIGINT NOT NULL, SORTORDER BIGINT, SHELVE_ID BIGINT, PRIMARY KEY (ID));
CREATE TABLE ABSTRACTPRODUCT (SHELVE_ID BIGINT);
The last table ABSTRACTPRODUCT should not be created since it is an Abstract Java Entity and I'm using the table per class inheritance style. This seems to be a bug in eclipselink it is also discussed in this question: Understanding of TABLE_PER_CLASS with keys in eclipselink It is the combination of the inheritance with the OneToMany relations that seems to trigger the create table statement. Not sure if this bug is related to the query error that I mentioned at the start. I think not as this table doesn't even have the sort order field.
When I remove the ORDER BY clause the query will execute succesfully. When I change the query to only go one level up it will also execute succesfully:
SELECT pr FROM AbstractProduct pr WHERE pr.shelve.id = :1 ORDER BY pr.sortOrder
For a test I got rid of the inheritance and let the Shelve entity have a OneToMany relation to Apple directly, where Apple did not extend any other class, in that case the query is also executed succesfully. But I need the abstract class and inheritance.
Any idea why the generated query is wrong in this case?
As is noted in an answer below I could use a different inheritance strategy to solve this problem in a different way. I choose the table per class type because that allows me to use the abstract entity in queries and the concrete classes get a table with all the fields in it. I was hoping that this helps with performance when doing a lot of inserts and selects from the concrete classes because that would only involve a single db table.
Update
I think this is an error in EclipseLink I have created two bugreports:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=549866 for the abstract class creating a table
https://bugs.eclipse.org/bugs/show_bug.cgi?id=549868 for the error in the query
I was able to reproduce your problem with the inheritance strategy TABLE_PER_CLASS. The query executed as expected once I changed it to InheritanceType.JOINED and recreated the schema of the database (in my case: PostgreSQL 10.9).
So the code should be changed to:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProduct {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
protected Shelve shelve;
protected long sortOrder;
}
Note well:
I added the missing #ManyToOne annotation to the attribute shelve.
#ManyToOne should also be added to protected SuperMarket superMarket; in the entity Shelve.
The resulting DB schema looks as follows:
CREATE TABLE public.abstractproduct
(
id bigint NOT NULL,
dtype character varying(31) COLLATE pg_catalog."default",
sortorder bigint,
shelve_id bigint,
CONSTRAINT abstractproduct_pkey PRIMARY KEY (id),
-- FK definitions left out for brevity
)
And Apple, for instance, becomes:
CREATE TABLE public.apple
(
id bigint NOT NULL,
CONSTRAINT apple_pkey PRIMARY KEY (id),
-- FK definitions left out for brevity
)
Hope it helps.

Hibernate postgres auto increment after manual insert

I have a basic spring application, with a simple entity. I have a flyway script, to create the postgres table, and add some starting data.
create table user (
id serial primary key,
username varchar (50) unique not null,
password varchar (150) not null
);
insert into user (id, username, password) values (1, 'name', 'somehashed');
insert into etc...
I've set up my entity as follows:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, columnDefinition = "serial")
private Long id;
...
other fields, constructor, getters setters etc...
My problem is, that on start-up, the basic entities are persisted by flyway, but upon trying to save a new entity, hibernate tries to give it the ID 1, although it is already given to another one.
I tried it also with SEQUENCE strategy, the problem didn't get solved.
Ok, problem was that I specified explicitly the ID I wanted to give while the insert script, and I didn't let postgres do the magic...

Error in Sequence lookup in Hibernate for Postgresql

I am having problem in inserting data into Postgresql through Hibernate when Primary column has auto-increment.
I have gone through multiple post in this forum also, but can't find a solution that works for me.
My Table and Sequence are defined in "apiprofile" schema. When I run the code, it is unable to find the Sequence name.
Even if I mention it with the schema.sequence, still it doesn't work.
Any help is appreciated.
Below are the code snippet and exception I am facing.
Sequence & Table:
CREATE SEQUENCE apiprofile.login_session_id_seq;
CREATE TABLE apiprofile.login_session (
id bigint NOT NULL DEFAULT nextval('apiprofile.login_session_id_seq'),
username varchar(255) NOT NULL,
token varchar(500) NOT NULL,
active_ind boolean NOT NULL,
login_time timestamp NOT NULL,
PRIMARY KEY (id)
);
Bean file
public class LoginSession {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "session_generator")
#SequenceGenerator(name="session_generator", sequenceName = "apiprofile.login_session_id_seq", schema = "apiprofile", allocationSize=1)
#Column(name="id", updatable = false, nullable = false)
private Integer id;
Insert operation
LoginSession session = new LoginSession();
session.setUsername(userName);
session.setToken(token);
session.setActive(true);
session.setLoginTime(new Timestamp(System.currentTimeMillis()));
getSession().saveOrUpdate(session);
Exception
Hibernate:
select
next_val as id_val
from
login_session_id_seq for update
could not read a hi value
org.postgresql.util.PSQLException: ERROR: relation "login_session_id_seq" does not exist
Position: 32
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2103) ~[postgresql-9.1-901-1.jdbc4.jar:?]
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1836) ~[postgresql-9.1-901-1.jdbc4.jar:?]
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257) ~[postgresql-9.1-901-1.jdbc4.jar:?]

How exactly work the #OneToMany JPA annotation in this example? Is it related to a table column or an entity class field?

I have 2 DB tables named respectivelly T_ACCOUNT and T_ACCOUNT_BENEFICIARY.
These tables have the following structure:
create table T_ACCOUNT (ID integer identity primary key, NUMBER varchar(9), NAME varchar(50) not null, CREDIT_CARD varchar(16), unique(NUMBER));
create table T_ACCOUNT_BENEFICIARY (ID integer identity primary key, ACCOUNT_ID integer, NAME varchar(50), ALLOCATION_PERCENTAGE decimal(5,2) not null, SAVINGS decimal(8,2) not null, unique(ACCOUNT_ID, NAME));
And the T_ACCOUNT table is bound to the T_ACCOUNT_BENEFICIARY table with a one to many relationship, this is the graphical representation:
So this is the first class named Account that map the T_ACCOUNT table:
#Entity
#Table(name="T_ACCOUNT")
public class Account {
#Id
#GeneratedValue
#Column(name="id")
private Long entityId;
#Column(name="NUMBER")
private String number;
#Column(name="NAME")
private String name;
#OneToMany
#JoinColumn(name="ACCOUNT_ID")
private Set<Beneficiary> beneficiaries = new HashSet<Beneficiary>();
#Column(name="CREDIT_CARD")
private String creditCardNumber;
// GETTERS & SETTERS
}
And this is the Beneficiary class that map the T_ACCOUNT_BENEFICIARY table:
/**
* A single beneficiary allocated to an account. Each beneficiary has a name (e.g. Annabelle) and a savings balance
* tracking how much money has been saved for he or she to date (e.g. $1000).
*/
#Entity
#Table(name="T_ACCOUNT_BENEFICIARY")
public class Beneficiary {
#Id
#GeneratedValue
#Column(name="ID")
private Long entityId;
#Column(name="NAME")
private String name;
#Embedded
#AttributeOverride(name="value",column=#Column(name="ALLOCATION_PERCENTAGE"))
private Percentage allocationPercentage;
#Embedded
#AttributeOverride(name="value",column=#Column(name="SAVINGS"))
private MonetaryAmount savings = MonetaryAmount.zero();
As you can see into the Account I have the beneficiaries field that implement the one to may relationship
#OneToMany
#JoinColumn(name="ACCOUNT_ID")
private Set<Beneficiary> beneficiaries = new HashSet<Beneficiary>();
I know that, on the DB, this relationship is implemented by the ACCOUNT_ID field of the T_ACCOUNT_BENEFICIARY table (so multiple row of the T_ACCOUNT_BENEFICIARY table can have the same value of the ACCOUNT_ID field and this means that a single row of the T_ACCOUNT table can be associated to more than one rows of T_ACCOUNT_BENEFICIARY table).
As you can see in the previous sippet there is the #JoinColumn(name="ACCOUNT_ID") annotation.
My doubt is generated by the fact that I have an ACCOUNT_ID column on my T_ACCOUNT_BENEFICIARY table, infact:
create table T_ACCOUNT_BENEFICIARY (ID integer identity primary key, ACCOUNT_ID integer, NAME varchar(50), ALLOCATION_PERCENTAGE decimal(5,2) not null, SAVINGS decimal(8,2) not null, unique(ACCOUNT_ID, NAME));
but this column seems to not be mapped on the Beneficiary that map this T_ACCOUNT_BENEFICIARY table.
So my doubts is: the #JoinColumn(name="ACCOUNT_ID") is working at relational level performing the join operation on the ACCOUNT_ID column of the table mapped by the Beneficiary entity (T_ACCOUNT_BENEFICIARY) or am I missing something? How exactly is performed this join?
If my interpretation is right can I work at entity level and say to join the beneficiaries field of my Account entity class to a new accountId field inserted into my Beneficiary entity class and mapping the ACCOUNT_ID column of the T_ACCOUNT_BENEFICIARY table?
Tnx
It seems is a Unidirectional OneToMany relationship
In JPA 2.0 a #JoinColumn can be used on a OneToMany to define the foreign key
I'm not sure if I understand your question. But what you have done with your #JoinColumn annotation is correct and Hibernate will execute appropriate SQL statements when you have multiple beneficiaries for your account. For example executing multiple INSERTS if you have two Beneficiaries for an Account. And yes using the #JoinColumn annotation is at the hibernate level. If you want to access an Account from a Beneficiary entity you would need to define a Bidirectional relationship in the Beneficiary class like below.
#Entity
#Table("T_ACCOUNT_BENEFICIARY")
public class Beneficiary {
#ManyToOne(mappedBy = "beneficiaries")
Account account;
...
}