Persist One to One Relation with Postgres Sequence - postgresql

I'm playing arround with JavaEE in my free time since a few Months
I have 2 Tables in Postgres
Table person
create table person(id bigint primary key default nextval('person_id_seq'), persontype varchar(5), name varchar(50), ERP_id smallint);
Table address
create table address (customer_id bigint primary key NOT NULL references person(id), department varchar(50), street varchar(50), plz varchar(10), city varchar(50), country varchar(50));
Model person
public class Person implements Serializable {
#Id
#SequenceGenerator(
name="person_id_seq",
sequenceName="person_id_seq",
allocationSize=1,
initialValue=1
)
#GeneratedValue(
strategy=GenerationType.AUTO,
generator="person_id_seq"
)
private Long id;
private char personType;
private String name;
private Long erp_id;
#OneToOne (cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.REFRESH})
#JoinColumn(name="id", insertable=false, updatable=false)
private Address address;
[...]
#PrePersist
public void initializeCustomerId(){
this.address.setCustomerId(id);
}
Model address
public class Address implements Serializable {
#Id
#GeneratedValue(
strategy=GenerationType.AUTO)
#Column(name="person_id")
private Long personId;
private String department;
private String street;
private int plz;
private String city;
private String country;
I've got this solution from https://blogs.oracle.com/WebLogicServer/entry/ejb3_mapping_of_one-to-one_rel
My Problem now... before I persist I need the id which will be generated by the Postgres-Sequence.
My Thought on this: in #PrePersist I could do an
select nextval('person_id_seq');
BUT What if there are more Clients who wants to save at the same time!!
So, how could I solve this Issue? Or am I completely wrong on this?
Thanks
Paddie

Related

JPA repository retrieve custom primary key value after save

I have an entity class pointing to postgresql table. Below is table structure. The paymentreferencenumber is the PK which is populated by a trigger. id field is the sequence generated field. When i try to save in this table using JPARepository save method it inserts the first record. But after that it fails due to the primary key constraint. Since PK is a string type and generated using trigger I am specifying generator strategy as 'select'. Can anyone help me with this blocker and point me in the right direction. Thanks
Table structure --
custId serial not null,
paymentreferencenumber varchar(32) not null
constraint customers1_pkey
primary key,
firstname varchar(255),
lastname varchar(255)
Entity class --
#Entity
#Table(name = "customersnew")
public class Customer implements Serializable {
private static final long serialVersionUID = -1L;
#GeneratedValue(generator = "seq")
#GenericGenerator(name="seq", strategy="sequence", parameters = { #Parameter(name="key", value = "customersnew_custid_seq")})
#Column(name = "custid")
private long id;
#Id
#GeneratedValue(generator = "trigger_generated")
#GenericGenerator(name="trigger_generated", strategy="select", parameters = { #Parameter(name="key", value = "id")})
#Column(name = "paymentreferencenumber")
private String refNum;
#Column(name = "firstname")
private String firstName;
#Column(name = "lastname")
private String lastName;
}
--- Controller using JPA save
#RestController
public class CustomerController {
#Autowired
CustomerRepository repository;
EntityManagerFactory emf;
public CustomerController(CustomerRepository repository, EntityManagerFactory emf) {
this.repository = repository;
this.emf = emf;
}
#PostMapping("/create")
public String create(#RequestBody CustomerUI customer){
// save a single Customer
Customer returnObj = repository.saveAndFlush(new Customer(customer.getFirstName(), customer.getLastName()));
PersistenceUnitUtil util = emf.getPersistenceUnitUtil();
Object retObj = util.getIdentifier(returnObj);
return "Customer is created";
}
If you don't specify an id generation strategy, Hibernate will use GenerationType.AUTO. This will result in any of
AUTO - either identity column, sequence or table depending on the
underlying DB.
If you look here, you'll notice all of those generate ids of type long, short or int, not of type String.
Say you wanted a String UUID as an id, you could use
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "paymentreferencenumber")
private String refNum;

JPA auto-generated key not reflected in foreign key of child table

Parent Table:
#Table(name="parent_table_t")
public class ParentTable implements Serializable {
#Id
#Column(name="contact_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer contactId;
---------
---------
#OneToOne (cascade = CascadeType.ALL, mappedBy = "parentTable")
private ChildTable childTable;
}
Child Table:
#Table(name="child_table_t")
public class ChildTable implements Serializable {
#Id
#Column(name="child_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer childId;
#Column(name="contact_id")
private Integer contactId;
#JoinColumn(name="contact_id", referencedColumnName = "contact_id", insertable=false, updatable=false)
#OneToOne(cascade = CascadeType.ALL)
private ParentTable parentTable;
}
My requirement is when contact_id is generated in Parent_table_t, it should be copied into contact_id of child_table_t when saved.
When I am calling saveAndFlush / save on Parent Table Entity, it is:
Generating the auto-increment for Parent->contact_id.
But Child_table_t -> contact_id is always null.
Can someone please help in this.
I am using in-memorty hsqldb with spring-boot and JPA.
You marked the relationship #JoinColumn with insertable=false, updatable=false, likely because you have an integer mapping for the column as well. Unfortunately, these settings prevent JPA from setting it with values from the relationship, which instead is forced to set the column with the value in the contactId attribute.
Put the insertable=false, updatable=false on the #Column instead.

JPA: create constraint between primary key column in one table and a non-primary column in another?

I’m using JPA and trying to figure out how to create a unique constraint between a primary key column in one table and a non-primary key column in another. I have two tables:
Customer (
id character varying(32) NOT NULL,
customer_name character varying(50)
)
and
Account (
id character varying(32) NOT NULL,
account_name character varying(50)
)
There is a unidirectional, one-to-many relationship between Customer and Account with a constraint that Account.account_name is unique per Customer. The code looks like so:
#Entity
public class Customer {
#Id
#Column(length=32)
private String id;
#Column(unique=true, length=50)
private String customer_name;
#OneToMany
private List<Account> accounts;
...
}
and
#Entity
public class Account {
#Id
#Column(length=32)
private String id;
#Column(length=50)
private String account_name;
...
}
A join table is created by default:
CUSTOMER_ACCOUNTS (
customer_id character varying(32),
accounts_id character varying(32)
)
How do I create the unique constraint to ensure an Account.account_name is unique per Customer?
Add a JoinColumn annotation to the Customer class::
#Entity
public class Customer {
#Id
#Column(length=32)
private String id;
#Column(unique=true, length=50)
private String customer_name;
#OneToMany(mappedBy="customer")
#JoinColumn(name="CUSTOMER_ID", referencedColumnName="ID")
private List<Account> accounts;
...
}
Referenced column name points to the primary key of the Customer table, while name points to the foreign key field of the Account table.
You should also add a CUSTOMER_ID field and a composite unique constraint to the account entity:
#Entity
#Table(
uniqueConstraints=
#UniqueConstraint(columnNames={"CUSTOMER_ID", "ACCOUNT_NAME"})
)
public class Account {
#Id
#Column(length=32)
private String id;
#Column(length=50)
private String account_name;
#Column(name="CUSTOMER_ID")
private String customer_id;
...
}

Declare JPA entity for a tree element

I do have a DB table with unidirectional trees. Leafs of these trees can have several children/parents.
Cycles are restricted.
Here is my DB table definition:
CREATE MULTISET TABLE WORKFLOW_SEQ_REL ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL,
REL_WORKFLOW_SEQ_ID INTEGER NOT NULL,
JOB_ID BIGINT)
PRIMARY INDEX ( WORKFLOW_SEQ_ID );
As you can see it doesn't have a primary key right now. But it would appear later :) Really PK is: JOB_ID+PARENT_ID+CHILD_ID.
The idea is:
REL_WORKFLOW_SEQ_ID = PARENT
WORKFLOW_SEQ_ID = CHILD
JOB_ID = TREE_IDENTIFICATOR (a determinant to separate different trees stored
in one table).
I'm trying to declare a JPA entity:
#Entity
#Table(name="WORKFLOW_SEQ_REL")
public class EtlWorkflowSeqNode {
#EmbeddedId
public EtlWorkflowSeqNodeId etlWorkflowSeqNodeId;
//#Column(name="JOB_ID")
//public Integer jobId;
#Embeddable
class EtlWorkflowSeqNodeId{
#Column(name="JOB_ID")
public Integer jobId;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="REL_WORKFLOW_SEQ_ID")
//EtlWorkflowSeq.id = PK of EtlWorkflowSeq entity
public EtlWorkflowSeq parent;
#OneToMany(fetch=FetchType.EAGER /*, mappedBy="parent"*/)
#JoinColumn(name="WORKFLOW_SEQ_ID")
public Set<EtlWorkflowSeq> children;
}
}
And I gen an error:
Caused by: org.hibernate.AnnotationException: A Foreign key refering models.EtlWorkflowSeqNode from models.EtlWorkflowSeq has the wrong number of column.
should be 2
Here is EtlWorkflowSeq entity:
#Entity
#Table(name="WORKFLOW_SEQ")
public class EtlWorkflowSeq {
#Id
#Column(name="WORKFLOW_SEQ_ID")
public Integer id;
#OneToOne(fetch=FetchType.EAGER)
#JoinColumn(name="WORKFLOW_ID")
public EtlWorkflow etlWorkflow;
}
What do I do wrong?
UPD:
Here are table definitions:
--a bad design. PK should be: WORKFLOW_SEQ_ID + REL_WORKFLOW_SEQ_ID + JOB_ID
CREATE MULTISET TABLE WORKFLOW_SEQ_REL ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL, --a ref to child
REL_WORKFLOW_SEQ_ID INTEGER NOT NULL, -- a ref to parent
START_TYPE_ID SMALLINT NOT NULL, -- a type of connection
DISABLE_START_TYPE_ID SMALLINT, -- other type of connection
JOB_ID BIGINT) -- a tree determinant,
PRIMARY INDEX ( WORKFLOW_SEQ_ID );
CREATE MULTISET TABLE WORKFLOW_SEQ ,NO FALLBACK ,
NO BEFORE JOURNAL,
NO AFTER JOURNAL,
CHECKSUM = DEFAULT,
DEFAULT MERGEBLOCKRATIO
(
WORKFLOW_SEQ_ID INTEGER NOT NULL, -- an id
WORKFLOW_ID BIGINT NOT NULL, -- a ref to original workflow, not interesting
IS_NAME VARCHAR(255) CHARACTER SET UNICODE NOT CASESPECIFIC, -- some name
INFO_SYSTEM_INST_CD VARCHAR(255) CHARACTER SET UNICODE NOT CASESPECIFIC, -- other name
DISABLE BYTEINT) -- so garbage
UNIQUE PRIMARY INDEX ( WORKFLOW_SEQ_ID ); -- it should also be a PK
The Idea is that several trees are stored in WORKFLOW_SEQ_REL
JOB_ID is a determinant for trees.
WORKFLOW_SEQ_ID, REL_WORKFLOW_SEQ_ID refer some cutomized template from REL_WORKFLOW_SEQ table.
I cannot help noticing that there is an inconsistency in your question.
You first state that:
Leafs of these trees can have several children/parents.
This in my believe makes the relationship between leafs many to many.
As I make of your question that EtlWorkflowSeq represent leafs, I think EtlWorkflowSeqNode represents the relationship between EtlWorkflowSeq objects?
However, the nodes point to one parent and many children.
You can use something like this to create something similar:
#Entity
#Table(name="WORKFLOW_SEQ")
public class EtlWorkflowSeq
{
#Id
#GeneratedValue
#Column(name="WORKFLOW_SEQ_ID")
public Integer id;
#ManyToOne
#JoinColumn(name="WORKFLOW_ID")
public EtlWorkflow etlWorkflow;
#ManyToMany
#JoinTable(name = "WORKFLOW_SEQ_REL")
private Set<EtlWorkflowSeq> children;
#ManyToMany(mappedBy = "children")
private Set<EtlWorkflowSeq> parents;
#ManyToOne
#JoinColumn(name = "JOB_ID", referencedColumnName = "id")
private Job job;
}
This would make EtlWorkflowSeqNode and EtlWorkflowSeqNodeId obsolete.
I also would like to state that when using an #Embeddable you should only use base types in them. Using other than base types is not possible/causes problems/is not standard (correct me if I'm wrong).
If you would like to use foreign keys in a composite primary key you can use this:
#Entity
public class Foo
{
#Id
private Long id;
}
#Entity
public class Bar
{
#EmbeddedId
private BarPK key;
#MapsId(value = "fooId")
#ManyToOne
#JoinColumns({
#JoinColumn(name = "foo_id", referencedColumnName = "id")
})
private Foo foo;
}
#Embeddable
public class BarPK
{
#Column(name = "id")
private Long id;
#Column(name = "foo_id")
private Long fooId;
}

Mapping JPA entity relationships

I would like some advice on how to best layout my JPA entity classes. Suppose I have 2 tables I would like to model as entities, user and role.
Create Table users(user_id primary key,
role_id integer not null )
Create table role(role_id primary key,
description text,
)
I create the following two JPA Entities:
#Entity
#Table(name="users")
#Access(AccessType.PROPERTY)
public class User implements Serializable {
private Long userId;
private Long roleId;
private Role role;
#Column(name = "user_id")
#Id
public Long getUserId() {}
#Column(name = "role_id")
public Long getRoleId() {}
#ManyToOne()
JoinColumn(name="role_id")
public Role getRole() {}
}
Role Entity:
#Entity
#Table(name="Role")
#Access(AccessType.PROPERTY)
public class Role implements Serializable {
private String description;
private Long roleId;
#Column(name = "role_id")
#Id
public Long getRoleId() {}
#Column(name = "description")
public Long getDescrition(){}
#ManyToOne()
#JoinColumn(name="role_id")
public Role getRole() {}
}
Would the correct way to model this relationship be as above, or would I drop the private Long roleId; in Users? Any advice welcomed.
When I map it this way, I receive the following error:
org.hibernate.MappingException: Repeated column in mapping for entity:
Yes, you would drop the private Long roleId mapping when you have a #ManyToOne on the same column.
As the error implies, you can only map each column in an #Entity once. Since role_id is the #JoinColumn for the #ManyToOne reference, you cannot also map it as a property.
You can, however, add a convenience method to return the role ID, like
public Long getRoleId() {
return role.getId();
}