JPA- Hibernate - ID values generated from Select statement - java

I am having problems mapping a legacy database schema using JPA annotations.
The schema uses a table to generate unique IDs for most of the tables in the db - it has a structure of:
table_name
column_name
next_id
block_size
To get new unique ID for a record insert, you run a select like:
SELECT next_id FROM tbl_next_id WHERE table_name = 'tbl_pets' and column_name = 'pet_id'
urrgh. I know I could do this in pure hibernate by using a 'select' id generator (unless I just dreamt that up) - can anyone recommend the best way of handling this with JPA annotations?
Thanks

There is nothing better than this:
#TableGenerator(name="petsgen",
table="tbl_next_id",
pkColumnName="table_name",
pkColumnValue="tbl_pets",
valueColumnName="next_id",
allocationSize=1
)
#Id
#GeneratedValue(strategy=GenerationType.TABLE, generator="petsgen")
Will of course not support combination of table name + column name, but should work as long as there is only one generator for table.

TableGenerator does something similar, but not exactly. There is no support for column_name and block_size. Maybe you can extend that generator to take care of it.

Related

Hibernate Generation Type for SQL Server and Others

I wrote program that uses different databases like sql server, oracle etc. My problem is that I can't handle GenerationType and insert correct row into table. Using GenerationType.AUTO and hibernate.id.new_generator_mappings := false in sql server, my program is able to insert new row into table, but ID is always null, same problem is when GenerationType is IDENTITY.
I tried to add auto-incrementation only for sql server, but Liquibase yells at me that it's not supported for mssql. When I use Sequences for Oracle as well SQL Server my program is trying to get "next value" from generator but it cannot and do infinite loop. Even if I set default value for ID it won't increment this value.
Thats my code :
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "name")
#SequenceGenerator(name = "name", sequenceName = "SEQ", allocationSize = 1)
private Long id;
I would like to be able to add auto-incrementing indices into table and it should work for SQL Server databases and I don't want to use Table strategy for generation because it needs additional table in db.
Problem solved. I add condition in Liquibase xml file that checks whether db is mssql type and if it's true script drops ID column and adds it with IDENTITY(1,1) option.
The only problem is that now I have to switch aforementioned "hibernate.id.new_generator_mappings" setting.

Should hibernate use unique sequences for each table?

I have several entities using AUTO key generation strategy with Hibernate and postgres.
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
This will result in a hibernate_sequence generated, and each entity will make use of that sequence when assigning keys.
Now I have a table that has lots of cache data (like 100k entries), and some user tables. As both use strategy AUTO, they both get their keys from the same hibernate sequence. As a result, even if I only have 10 users, they will all have an id of 6-7 digits long, like 123123.
I wonder if, in general, one should introduce a custom sequence for each table? Or shouldn't I care about the id generation that much?
I have recently solved this problem for my project. I use the Enhanced sequence generator (which is the default for sequence-style generators) and set the prefer_sequence_per_entity parameter to true.
Contents of my package-info.java:
#GenericGenerator(
name = "optimized-sequence",
strategy = "enhanced-sequence",
parameters = {
#Parameter(name="prefer_sequence_per_entity", value="true"),
#Parameter(name="optimizer", value="hilo"),
#Parameter(name="increment_size", value="50")})
package org.example.model;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
On the usage side you just need
#Id #GeneratedValue(generator="optimized-sequence")
public long id;
I prefer having separate sequences because occasionally I'll drop a table and recreate it, and I want the ID's starting from one.
You can use serial datatype for your useid , or use PostgreSQL sequence.
LIKE :
digoal=# create table tt(id serial, info text);
CREATE TABLE
digoal=# insert into tt (info) values ('test'),('test');
INSERT 0 2
digoal=# select * from tt;
id | info
----+------
1 | test
2 | test
(2 rows)
OR
digoal=# create table tt1(id int, info text);
CREATE TABLE
digoal=# create sequence seq_tt1;
CREATE SEQUENCE
digoal=# alter table tt1 alter column id set default nextval('seq_tt1'::regclass);
ALTER TABLE
digoal=# insert into tt1 (info) values ('test'),('test');
INSERT 0 2
digoal=# select * from tt1;
id | info
----+------
1 | test
2 | test
(2 rows)
Try with Sequence
First create sequence in postgres
CREATE SEQUENCE YOUR_ENTITY_SEQ;
In entity, use generation strategy as SEQUENCE and for next time gen value allocation set allocationSize as necessary
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "YOUR_ENTITY_SEQ")
#SequenceGenerator(name = "YOUR_ENTITY_SEQ", sequenceName = "YOUR_ENTITY_SEQ", allocationSize = 1)
private Long id;
This is just the preferred way for Hibernate to manage primary keys. It will generate the appropiate SQL idiom depending on your underlying database (a sequence for Oracle, and identity key field for DB2, etc.)
However, you can perfectly define composite keys if you feel they are more appropiate for your business. Someone gave a great explanation on this here in Stackoverflow:
How to map a composite key with Hibernate?

How to get Column names Column Data types Form Table using Eclipse Link/JPA

I am working on JPA. My requirement is get the Column Name and Data types from Table.
I have Query's to do that but those are Native Query's. If I used those Native Query's, Is It will support on any Data Base like Oracle, MySql,......
Now am Using MySql with JPA working fine.
Below Query for Getting Table Column Names
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = 'SchemaName'
AND TABLE_NAME = 'TableName';
I executed above Query using createNativeQuery() in JPA . Is it will support in all Data Bases. If not then how can I do this. Thank you very much.
In Oracle you can use ALL_TAB_COLUMNS table and in MS SQL server and MySQL you can use INFORMATION_SCHEMA.COLUMNS table to get information about tables and columns.
SELECT * FROM ALL_TAB_COLUMNS
WHERE OWNER = 'SchemaName' AND TABLE_NAME = 'TableName';

Hibernate populate primary key using database trigger

I am using Hibernate 4.1.0.Final with Spring 3
I have the following in Entity class
#Id
#Column(name = "PROJECT_NO")
#GeneratedValue(strategy=GenerationType.TABLE)
private String projectNumber;
Is it possible to use database trigger to populate the primary key of a table? Or I have to use a CustomGenerator for this?
When I tried the above I have the following exception
org.hibernate.id.IdentifierGenerationException: Unknown integral data type
for ids : java.lang.String
Database trigger doesn't have any sequence, it is using
SELECT NVL (MAX (project_no), 0) + 1 FROM projects
Edit 1
#GeneratedValue(generator="trig")
#GenericGenerator(name="trig", strategy="select",
parameters=#Parameter(name="key", value="projectNo"))
The above throws the following exception
Hibernate: select PROJECT_NO from PROJECTS where PROJECT_NO =?
java.lang.NullPointerException
exception in save null
at org.hibernate.tuple.entity.AbstractEntityTuplizer.getPropertyValue(AbstractEntityTuplizer.java:645)
at org.hibernate.persister.entity.AbstractEntityPersister.getPropertyValue(AbstractEntityPersister.java:4268)
at org.hibernate.id.SelectGenerator$SelectGeneratorDelegate.bindParameters(SelectGenerator.java:138)
at org.hibernate.id.insert.AbstractSelectingDelegate.performInsert(AbstractSelectingDelegate.java:84)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2764)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3275)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:81)
The problem is that you're using a String instead of a numeric value. Use a Long instead of a String, and your error will disappear.
AFAIK, you can't use a trigger to populate the ID. Indeed, Hibernate would have to retrieve the generated ID, but since it doesn't have an ID, I don't see how it could read back the row it has just inserted (chicken and egg problem).
You could use your SQL query to get an ID before inserting the row, but this strategy is inefficient, and has a risk of duplicate IDs in case of concurrent inserts. So I wouldn't use this strategy. You tagged your post with Oracle. I suggest you use a sequence. that's what they're for.
As of this on the Hibernate 3.3 documentation page you can do that.
select
retrieves a primary key, assigned by a database trigger, by selecting
the row by some unique key and retrieving the primary key value.

SQLiteDatabase delete involving sub query

I am using SQLiteDatabase for a Java library, and I need to support a very low version of the Android API (v4), which doesn't ship with SQLite version that supports Foreign Keys.
Therefore in order to delete a top level piece of data and all it's "children", I need to delete these children before in such a manner to manually reproduce the same effect as the Foreign Key constraint ON DELETE CASCADE
What I'm trying to do is the following SQL with the delete api.
DELETE FROM childTable
WHERE someFK IN (
SELECT parent_id
FROM parentTable
WHERE someFlag = 1
)
The initial solution I came up with was to hard code the select query in my where clause as follows, however since the SQLiteDatabase api supports query's, execSQL etc, is this solution I used horribly wrong and dangerous to use ?
Workaround:
String[] whereArgs = {Integer.toString(1)};
this.database.delete(TABLE_CHILD_ONE, COL_ONE_FK_SESSION+" IN (SELECT "+COL_SESSION_ID+" FROM "+TABLE_SESSIONS+" WHERE "+COL_SESSION_DONE+"=?)", whereArgs);
this.database.delete(TABLE_CHILD_TWO, COL_TWO_FK_SESSION+" IN (SELECT "+COL_SESSION_ID+" FROM "+TABLE_SESSIONS+" WHERE "+COL_SESSION_DONE+"=?)", whereArgs);
this.database.delete(TABLE_CHILD_THREE, COL_THREE_FK_SESSION+" IN (SELECT "+COL_SESSION_ID+" FROM "+TABLE_SESSIONS+" WHERE "+COL_SESSION_DONE+"=?)", whereArgs);
You can create trigger for each table:
CREATE TRIGGER delete_cascade AFTER DELETE ON parentTable
BEGIN
DELETE FROM childTable child WHERE child.parent_id=OLD.id;
END;
Run it in transaction.
More about triggers

Categories