JPA/Hibernate: Update Parent column value before inserting child column value - java

This is my scenario. I have a Parent table Files_Info and a child table Files_Versions.
create table files_info(
id bigint primary key,
name varchar(255) not null,
description varchar(255) not null,
last_modified TIMESTAMP,
latest_version integer default 0 not null
);
create table files_versions(
id bigint primary key,
file_id bigint references files_info(id),
version integer not null,
location text not null,
created TIMESTAMP,
unique(file_id, version)
);
This is mainly to track a file and its various versions. When the user initiates a new file creation (not yet uploaded any version of the file), an entry is made to the files_info table with basic info like name, description. The latest_version will be 0 initially.
Then when the user uploads the first version, an entry is created in the files_versions table for that file_id and the version
value is set as parent's latest_version + 1. Parent's latest_version is now set to 1.
The user can also upload an initial version of the file when he/she initiates a new file creation. In that case, parent record
will be created with latest_version as 1 and also the corresponding version 1 child record.
I do not know how to design this using JPA / Hibernate.
I wrote my Entity and Repository classes and the save methods seem to work independently. But I do not know how to do the simultaneously latest_version updates.
Can this be done using JPA / Hibernate? Or should it be a database trigger?

A trigger is a valid option, but It can be done using JPA/Hibernate.
I'll suggest to use #PrePersist annotation on some method defined at the files_versions entity ... This method will be called by JPA when you execute: EntityManager.persist(FileVersion); and it can be use to update entity's derivative attributes ... In your case, will be the sum of the file last_version + 1 ... Example:
#Entity
#Table(name = "files_info")
public class FileInfo {
}
#Entity
#Table(name = files_versions)
public class FileVersion {
... //some attributes
#Column(name = "version")
private int version;
#ManyToOne
#JoinColumn(name = "file_id")
private FileInfo fileInfo;
... //some getters and setters
#PrePersist
private void setupVersion() {
// fileInfo should be set before of calling persist()!
// fileInfo should increase its lastest Version before of calling persist()!
this.version = this.fileInfo.getLastVersion();
}
}

Related

How to update records in existing column using Spring JPA?

I am new to Java and spring. I have an existing database and table. I just wanted to update the records in the existing table. I have created a class and marked it with #entity annotation and I have spring.jpa.hibernate.auto-ddl set to update in application.properties.
But when I run my program it is creating new columns in the database. I don't want new columns to be added. Just wanted to map the table to the class and update existing records in the table.
Also, my table has 4 columns of which one has not null constraint on it. So when I run the program it's giving me an error saying "ALTER TABLE only allows columns to be added that can contain nulls, or have a DEFAULT definition specified, or a column being added is an identity, or timestamp column or alternatively if none of the previous conditions are satisfied the table must be empty to allow the addition of this column. Column brand_id cannot be added to a not empty table TableName because it does not satisfy these conditions." I could see on the console that it's executing alter table add column command.
Column names in the table are brandId, advanceDescription, and Aliases.
#Entity
Public class TableName {
#Id
private int recid;
private int brandId;
private String advanceDescription;
private String aliases;
}
And the newly added column name in the table is advance_description.
If I understand correctly, you don't want to change the schema of the table. Just add new data to it. To do so you need to disable auto-ddl and set it to none:
spring.jpa.hibernate.auto-ddl=none
Now the second problem is mismatched column names. Try changing your entity definition by setting a matching column name for each field. You can do it by using #Column(name="columnName") annotation. With the provided data it should be something like this:
#Entity
public class TableName {
#Id
private int recid;
#Column(name="brandId")
private int brandId;
#Column(name="advance_description")
private String advanceDescription;
#Column(name="Aliases")
private String aliases;
}
Please try show create table tableName command on db.
it will give you the schema of your table like
CREATE TABLE `tableName` (
`recid` int(11),
`brand_id` int(11) unsigned NOT NULL,
`advance_description` text,
`aliases` text,
PRIMARY KEY (`recid`),
)
#Entity
#Table("tableName")
public class TableName {
#Id
private int recid;
#Column(name="brand_id")
private int brandId;
#Column(name="advance_description")
private String advanceDescription;
#Column(name="aliases")
private String aliases;
}
i.e. now you can see from the db command you will able to see that your table have brand_id field so you want to map brand_id field in your TableName.class with brandId field so in that case you have to add #Column(name = "brand_id) onto your brandId field i.e. in db field is brand_id but in java I want to map brand_id field to brandId.
This applicable to all column. Check for rec_id,advance_description and aliases too.

JPA : Re-use Id generated from a sequence for a new version of the same object having composite PK (ID + VERSION)

I have entity A with composite PK [ id(generated from sequence) + version ].
For a brand new record I want to pick the id from a sequence defined in the DB side.
Lets say its created like below
ID VERSION
1 0
Next time, I want a new version of the same Id to be created like below
ID VERSION
1 0
1 1
Note : in the second case I don't want it to be generated by the sequence generator, coz I want to manually provide it.
Is it possible in JPA/Hibernate ? If possible could someone please tell how to do it ?
Many thanks in advance!
Hibernate ORM doesn't support the generation of id with composite keys.
You can probably run a native SQL when you create a new object.
With PostgreSQL for example:
Long id = (Long) em.createNativeQuery("SELECT nextval('mysequence')").getSingleResult();
Long version = ...;
EmbeddedId id = new EmbeddedId(id, version);
Where EmebeddedId is the composite key of your entity:
#Entity
class Example {
#Id
EmbeddedId id;
...
}
#Embeddable
class EventId implements Serializable {
Long id;
Long version;
...
}
Where mysequence is the name of a sequence on the database.

Unique key auto generated in Spring Boot Entity

I have a primary key in my entity table which is autogenerated but now I want unique keys to be auto generated so how to do it
Please help me out.
#Entity
#Table(name = "director")
public class Director {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private long id;
//how to make this field auto generated like above one
#Column(name = "subid", unique=true)
private long sub_id;
My database table picture is here please refer
You can use timestamp or static AtomicLong counter as sub_id value. Try to define method with annotation #PrePersist in your entity class and your JPA provider will execute it before persisting an object.
Note: using timestamp in concurrent environment may lead to collisions and values won't be unique.
private final static AtomicLong subIdCounter = new AtomicLong(System.nanoTime());
#PrePersist
void sub_id() {
this.sub_id = subIdCounter.incrementAndGet();
}
After a short study it seems that that Hibernate supports the feature of generated values only with fields annotated with #Id. With #Id and default #generatedValue Hibernate creates - depending on the database and dialect used - appropriate way to generate the value of id field. usually this is something like creating a sequence and setting the column definition like (examples are from Postgres 12):
id bigint not null nextval('director_id_seq'::regclass)
Interesting thing is that this is done by issuing create statement like this:
create table director (id bigserial not null, primary key (id))
So, the column type bigserial actually generates sequence that is used to insert default value to the id column.
There are two options it you want to generate the value for column sub_id as it is generated to the column id. Both are database dependent.
Just create the sequence manually to the database and alter column sub_id to fetch the default value from the sequence.
OR
Change your column definition to use appropriate column type, like:
#Column(name = "subid", insertable = false,
nullable = false, unique = true, columnDefinition = "bigserial")
private long sub_id;
This will cause Hibernate to generate table like:
create table director (id bigserial not null, subid bigserial not null, primary key (id))
and result to a column like:
subid bigint not null nextval('director_subid_seq'::regclass)
But again: this is database specific stuff.
Also note: that JPA is aware only of the value that is stored to the id field. The subid is inserted to the database table but the sub_id field is not populated until entity is refreshed in its persistence context.

jooq - exclude id field when inserting from POJO

Is there anyway to insert a new record into a PostgreSQL database with Jooq straight from a POJO which extends a general identity class that has an id field without including the id in the insert statement?
An example POJO:
#Data
public abstract class PersistenceIdentity {
#Id
#Column(name = "id", unique = true, nullable = false, precision = 7, insertable = false)
private Integer id;
#Column(name = "created_date")
private LocalDateTime createdDate;
public abstract Table<R> getJooqTable();
}
#Data
public class SocialNetwork extends PersistenceIdentity {
#Column(name = "name")
private String name;
#Override
public Table<SocialNetworkRecord> getJooqTable() {
return Tables.SOCIAL_NETWORK;
}
}
The PostgreSQL schema is:
CREATE TABLE "social_network" (
id SERIAL NOT NULL PRIMARY KEY,
created_date TIMESTAMP DEFAULT now(),
name TEXT NOT NULL
);
My code to persist the POJO:
public <T extends PersistenceIdentity> T insertRecord(T record) {
Record newRecord = db.newRecord(record.getJooqTable(), record);
if (newRecord instanceof UpdatableRecord) {
((UpdatableRecord) newRecord).store();
}
return newRecord.into(record);
}
I realize I'm probably doing what Jooq really wasn't meant for (i.e. using generic types), however that (appears) to work just fine.
The problem is, Jooq includes the id in the insert statement and I then, of course, get a null value constraint. I don't want it inserted when it's a new record, however I do want it included when it returns the record (after inserting), when updating and also in select statements.
I can't simply exclude the id because I need it later on to easily get around some of the #OneToMany / #ManyToOne limitations.
And I would rather not have to insert the specific values for each POJO (that's why we annotated with #Column).
Does Jooq not honor the #Id or the insertable = false parameter in #Column?
Can anyone shed some light on this?
EDIT 1
Per request, below is the relevant snippet from the jOOQ generated table object. I'm not sure if this is correct or not for what I'm trying to do (i.e. allow the database to generate the ID), but I would think nextval('social_network_id_seq'::regclass) would accomplish that.
#Generated(
value = {
"http://www.jooq.org",
"jOOQ version:3.9.1"
},
comments = "This class is generated by jOOQ"
)
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class SocialNetwork extends TableImpl<SocialNetworkRecord> {
/**
* The column <code>public.social_network.id</code>.
*/
public final TableField<SocialNetworkRecord, Integer> ID = createField("id", org.jooq.impl.SQLDataType.INTEGER.defaultValue(org.jooq.impl.DSL.field("nextval('social_network_id_seq'::regclass)", org.jooq.impl.SQLDataType.INTEGER)), this, "");
}
Also, we use the mvn jooq-codegen:generate -Djooq.generator.name=org.jooq.util.XMLGenerator to generate the XML schema and then generate the the jOOQ table objects from that XML config. The thinking is we can push the XML config to github and all builds can simply regenerate the table objects from that.
Here is the XML:
<column>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
<column_name>id</column_name>
<data_type>integer</data_type>
<character_maximum_length>0</character_maximum_length>
<numeric_precision>32</numeric_precision>
<numeric_scale>0</numeric_scale>
<ordinal_position>1</ordinal_position>
<column_default>nextval('social_network_id_seq'::regclass)</column_default>
</column>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<constraint_type>PRIMARY KEY</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_1_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<table_constraint>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>2200_17431_3_not_null</constraint_name>
<constraint_type>CHECK</constraint_type>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</table_constraint>
<key_column_usage>
<column_name>id</column_name>
<constraint_catalog></constraint_catalog>
<constraint_schema>public</constraint_schema>
<constraint_name>social_network_pkey</constraint_name>
<ordinal_position>0</ordinal_position>
<table_catalog></table_catalog>
<table_schema>public</table_schema>
<table_name>social_network</table_name>
</key_column_usage>
EDIT 2
My SocialNetwork jOOQ-generated table object does not have a getIdentity() method, however it does have a getPrimaryKey() method and if it helps, my SocialNetworkRecord class has two Constructors:
public SocialNetworkRecord() {
super(SocialNetwork.SOCIAL_NETWORK);
}
/**
* Create a detached, initialised SocialNetworkRecord
*/
public SocialNetworkRecord(Integer id, Timestamp createdDate, String name) {
super(SocialNetwork.SOCIAL_NETWORK);
set(0, id);
set(1, createdDate);
set(2, name);
}
The way jOOQ works, there are two elements worth explaining:
Step 1: Record.from(Object):
Record newRecord = db.newRecord(record.getJooqTable(), record);
This call is convenience for this:
Record newRecord = db.newRecord(record.getJooqTable());
newRecord.from(record);
And the Record.from(Object) will copy all values from the record to the newRecord by using Record.set(Field, Object), which again sets the record's internal Record.changed(Field) flag.
Step 2: UpdatableRecord.store()
Your call to:
((UpdatableRecord) newRecord).store();
Will take all changed() fields into consideration for the relevant INSERT or UPDATE statement that is executed. The rationale here is that people sometimes want to set the primary key value explicitly, and not let an identity generate the value for them. Even if an identity is present on the primary key, it may sometimes be desireable to override its value. SQL standard databases (e.g. Oracle 12c) thus support two ways of specifying an identity:
-- This can be overridden
GENERATED BY DEFAULT AS IDENTITY
-- This can never be overridden
GENERATED ALWAYS AS IDENTITY
(MySQL's AUTO_INCREMENT or PostgreSQL's SERIAL type work the same way)
jOOQ assumes GENERATED BY DEFAULT AS IDENTITY here. The only exception to the above behaviour is when the identity column is NOT NULL and the Record value for the identity is null and jOOQ's meta model is aware of both:
- `NOT NULL` constraint
- `GENERATED BY DEFAULT AS IDENTITY`
Then, jOOQ will omit considering the identity value for insertion / update.
Bug in 3.9.2 and less:
Note that up until jOOQ version 3.9.2, there was a bug / missing feature in the XMLGenerator that produces the XML file you're importing: https://github.com/jOOQ/jOOQ/issues/6141. This bug resulted in no identity information being generated.
Workaround 1: If you cannot influence the jOOQ meta model
If, for some reason, you cannot get the jOOQ meta model to reflect your NOT NULL constraint and your DEFAULT clause, you could work around this limitation by resetting the value of the identity right after your Record.from(Object) call using Record.reset(Field):
Record newRecord = db.newRecord(record.getJooqTable(), record);
newRecord.reset(identityColumn);
((UpdatableRecord) newRecord).store();
Workaround 2: Generate a synthetic identity
The code generator has a feature to generate synthetic identities. For instance, if all your identity columns are called ID, you could write this:
<!-- fully qualified -->
<syntheticIdentities>.*?\.ID</syntheticIdentities>
Or this:
<!-- unqualified -->
<syntheticIdentities>ID</syntheticIdentities>

Hibernate get one column from a one to many connection

I want to create a java class that contains only 1 column from OneToMany ManyToOne etc. type connection not the whole row.
How can I do that?
(I'm not sure that I could express myself so I made an example)
TABLE e_skill
(
id int NOT NULL AUTO_INCREMENT,
skill_name VARCHAR (20) NOT NULL,
PRIMARY KEY (id)
);
TABLE t_person
(
id int NOT NULL AUTO_INCREMENT,
user_id int NOT NULL,
primary_skill int,
PRIMARY KEY (id),
FOREIGN KEY (primary_skill) REFERENCES e_skill(id)
);
TABLE t_secondaryskills
(
id int NOT NULL AUTO_INCREMENT,
t_person_id int NOT NULL,
skill_name int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (t_person_id) REFERENCES t_person(id),
FOREIGN KEY (skill_name) REFERENCES e_skill(id)
);
public enum Skill {
...
}
#Entity
#Table(name = "t_person")
public class Employee {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#Enumerated(EnumType.STRING)
//????????
//get skill_name column from e_skill
//????????
private Skill primarySkill;
#OneToMany
#Enumerated(EnumType.STRING)
//????????
//get skill_name column from e_skill
//????????
private Set<Skill> secondarySkills;
//getters setters
}
The only way I could do it now is to create a Entity to represent the e_skill table, I want to avoid that, because I only need 1 column from it.
If I understand your question correctly, you can't do what you want because of the secondary skills (because it's a collection). You can only map the primary skill name though using the #SecondaryTable annotation.
When you map things using an ORM there's no such thing as I only want a column in this scenario as you're mapping Objects, and usually in your objects you don't want to replicate data (unless they are outside your domain model). If this is unacceptable for you, I suggest you to take a look at other tools like myBtais, which gives you full control on the data you get back.
So bottom line, map your skill as an entity and live with it even if it has many columns, or choose a different tool (but not an ORM).

Categories