Update Statement with hibernate switching unique key fields - java

I'm not sure exactly where the error is coming from, unfortunately, but I have a guess and would like to know the best way to solve it.
Problem
Suppose we have the following table in the database
ID
Field A
Field B
Field C
1
A
C
Something
2
B
C
Something else
And we have two unique indexes on the table
Unique-Index1 (ID)
Unique-Index2 (FieldA, FieldB)
Now I am loading both entities
Session session = ...();
Transaction tx = session.beginTransaction();
TestTable dataset1 = (TestTable) session.get(TestTable.class, 1);
TestTable dataset2 = (TestTable) session.get(TestTable.class, 2);
And now I want to do something like this
update testtable set fielda = 'B' where id = 1;
update testtable set fielda = 'A' where id = 2;
So at the end the unique key is not violated, but after the first statement, the unique index is violated.
In my JAVA application it looks like this
dataset1.setFieldA("B");
dataset2.setFieldA("A");
session.saveOrUpdate(dataset1);
session.saveOrUpdate(dataset2);
tx.commit();
After executing the application I get the following exception
Could not execute JDBC batch update
Unfortunately, the error is not really meaningful. Also, I don't get any information whether it might be a duplicate or not. But if I delete the unique index, it works. So my guess is that it is because of that.
Used frameworks / systems
Java 17 SE application, using Hibernate 3.2 (very old version) with the legacy mapping XML files (so still without annotations). The database is an IBM Informix database.
The database model, as well as the indexes are not generated by Java, but by regular SQL scripts.
I can't change anything about the versions of Hibernate or the database either, unfortunately. Also I cannot influence how the index was created. This all happens outside the application.
Idea
The only idea I had was to first change all records that need to be changed to fictitious values and then set the correct values again. But that would mean that two update statements are triggered per record, right?
Something like this:
dataset1.setFieldA("XXX");
dataset2.setFieldA("YYY");
session.saveOrUpdate(dataset1);
session.saveOrUpdate(dataset2);
dataset1.setFieldA("B");
dataset2.setFieldA("A");
session.saveOrUpdate(dataset1);
session.saveOrUpdate(dataset2);
tx.commit();
However, I am not even sure if I need to commit the transaction. Maybe a flush or something similar is enough, but the solution is not really nice. I can kind of understand the problem, but I would also have thought that this would be legitimate within a transaction then - only at the end of the transaction the constraints have to be correct.
Many greetings and thanks for your help,
Hauke

You have two options. Either you configure the unique constraint to be "deferrable" and also mark it as "initially deferred" so that the constraint is only enforced at transaction commit time, or you delete and re-insert the entries.
I would suggest you to use the first option if your database supports this. You didn't specify which database you are using, but PostgreSQL supports it. You'd only have to run alter table test_table alter constraint your_unique_constraint deferrable initially deferred.

Related

Get identity after Instead of insert trigger

I am using Hibernate with MSSQL server writing the software that integrates with an existing database. There is an instead of insert trigger on the table that I need to insert into and it messes up ##Identity, which means on Hibernate's save I can't get the id of inserted row. I can't control the trigger (can't modify it). I saw this question, but it involves procedures, which my trigger does not have, so I thought my question is different enough. I can't post the whole trigger, but hopefully I can post enough to get the point across:
CREATE TRIGGER TrigName ON TableName
INSTEAD OF INSERT
AS
SET XACT_ABORT ON
BEGIN TRANSACTION
-- several DECLARE, SET statements
-- a couple of inserts into other tables for business logic
-- plain T-SQL statements without procedures or functions
...
-- this is the actual insert that i need to perform
-- to be honest, I don't quite understand how INSERTED table
-- was filled with all necessary columns by this point, but for now
-- I accept it as is (I am no SQL pro...)
INSERT INTO ClientTable (<columns>)
SELECT <same columns> from INSERTED
-- a couple of UPDATE queries to unrelated tables
...
COMMIT TRANSACTION;
I was wondering if there is a reliable way to get the id of the row being inserted? One solution I thought of and tried to make is to install an on insert trigger on the same table that writes the newly inserted row into a new table I added to the db. I'd use that table as a queue. After transaction commit in Hibernate I could go into that table and run a select with the info I just inserted (I still have access to it from the same method scope), and I can get the id and finally remove that row. This is a bulky solution, but best I can come up with so far.
Would really appreciate some help. I can't modify existing triggers and procedures, but I can add something to the db if it absolutely does not affect existing logic (like that new table and a on insert trigger).
To sum up: I need to find a way to get the ID of the row I just inserted with Hibernate's save call. Because of that instead of insert trigger, hibernate always returns identity=0. I need to find a way to get that ID because I need to do the insert in a few other tables during one transaction.
I think I found an answer for my question. To reply to #SeanLange's comment: I can't actually edit insert code - it's done by another application and inquiry to change that will take too long (or won't happen - it's a legacy application). What I did is insert another trigger on insert on the same table. Since I know the order of operations in the existing instead of insert trigger I can see that the last insert operation will be in the table I want so that means my on insert trigger will fire right after that. In the scope of that trigger I have access to inserted table out of which I pull out the id.
CREATE TRIGGER Client_OnInsert ON myClientTable
FOR INSERT
AS
BEGIN
DECLARE #ID int;
SET #ID = (select ClientID from inserted);
INSERT INTO ModClient (modClientId)
OUTPUT #ID
VALUES (#ID);
END
GO
Then in Hibernate (since I can't use save() anymore), I use a NativeQuery to do this insert. I set parameters and run the list() method of NativeQuery, which returns a List where the first and only argument is the id I want.
This is a bulky way, I know. If there is anything that's really bad that will stand out to people - please let me know. I would really appreciate some feedback on this. However, I wanted to post this answer as a potential answer that worked so far, but it does not mean it's very good. For this solution to work I did have to create another small table ModClient, which I will have to use as a temp id storage for this exact purpose.

Prevent violating of UNIQUE constraint with Hibernate

I have a table like (id INTEGER, sometext VARCHAR(255), ....) with id as the primary key and a UNIQUE constraint on sometext. It gets used in a web server, where a request needs to find the id corresponding to a given sometext if it exists, otherwise a new row gets inserted.
This is the only operation on this table. There are no updates and no other operations on this table. Its sole purpose is to persistently number of encountered values of sometext. This means that I can't drop the id and use sometext as the PK.
I do the following:
First, I consult my own cache in order to avoid any DB access. Nearly always, this works and I'm done.
Otherwise, I use Hibernate Criteria to find the row by sometext. Usually, this works and again, I'm done.
Otherwise, I need to insert a new row.
This works fine, except when there are two overlapping requests with the same sometext. Then an ConstraintViolationException results. I'd need something like INSERT IGNORE or INSERT ... ON DUPLICATE KEY UPDATE (Mysql syntax) or MERGE (Firebird syntax).
I wonder what are the options?
AFAIK Hibernate merge works on PK only, so it's inappropriate. I guess, a native query might help or not, as it may or may not be committed when the second INSERT takes place.
Just let the database handle the concurrency. Start a secondary transaction purely for inserting the new row. if it fails with a ConstraintViolationException, just roll that transaction back and read the new row.
Not sure this scales well if the likelihood of a duplicate is high, a lot of extra work if some percent (depends on database) of transactions have to fail the insert and then reselect.
A secondary transaction minimizes the length of time the transaction to add the new text takes, assuming the database supports it correctly, it might be possible for the thread 1 transaction to cause the thread 2 select/insert to hang until the thread 1 transaction is committed or rolled back. Overall database design might also affect transaction throughput.
I don't necessarily question why sometext can't be a PK, wondering why you need to break it out at all. Of course, large volumes might substantially save space if sometext records are large, it almost seems like you're trying to emulate a lucene index to give you a complete list of text values.

How to synchronize a code which is deployed on a cluster?

We have a stateless ejb which persists some data in an object oriented database. Unfortunately, today our persistence object does not have a unique key due to some unknown reason and altering the PO is also not possible today.
So we decided to synchronize the code. Then we check if there is an object already persisted with the name(what we consider should be unique). Then we decide to persist or not.
Later we realized that the code is deployed on a cluster which has three jboss instances.
Can anyone please suggest an idea which does not allow to persist objects with the same name.
If you have a single database behind the JBoss cluster you can just apply a unique contraint to the column for example (I am assuming its an SQL database):
ALTER TABLE your_table ADD CONSTRAINT unique_name UNIQUE (column_name);
Then in the application code you may want to catch the SQL exception and let the user know they need to try again or whatever.
Update:
If you cannot alter the DB schema then you can achieve the same result by performing a SELECT query before insert to check for duplicate entries, if you are worried about 2 inserts happening at the same time you can look at applying a WRITE_LOCK to the row in question

Error in Script File If I Restart Identifyer Column After Insert

Is it possible to restart the ID column of an HSQLDB after rows were inserted? Can I even set it to restart at a value lower than existing IDs in the table?
The Situation
I have a simple Java program which connects to a HSQLDB like so:
DriverManager.getConnection("jdbc:hsqldb:file:" + hsqldbPath, "", "");
This gives me an HsqlException when executing the following script (this is an excerpt, the complete script for HSQLDB 2.2.4 can be found here):
SET SCHEMA PUBLIC
CREATE MEMORY TABLE PUBLIC.MAP(
ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 0) NOT NULL PRIMARY KEY,
FOO VARCHAR(16) NOT NULL)
ALTER TABLE PUBLIC.MAP ALTER COLUMN ID RESTART WITH 1
// [...]
SET SCHEMA PUBLIC
INSERT INTO MAP VALUES(1,'Foo')
INSERT INTO MAP VALUES(2,'Bar')
ALTER TABLE PUBLIC.MAP ALTER COLUMN ID RESTART WITH 42
The message is:
HsqlException: error in script file: ALTER TABLE PUBLIC.MAP ALTER COLUMN ID RESTART WITH 42
The exception goes away when I move the RESTART-command before the INSERTs. The documentation gives no hint as to why that would be necessary.
I will eventually have to make this work on version 2.2.4 but have the same problem with the current version 2.3.2.
Background
What I am trying to do here is to recreate a situation which apparently occurred in production: An unlucky interaction with the database (I don't know what exactly happened) seems to have caused newly inserted rows to collide with existing ones because they were issued the same IDs. I want to create a test replicating the scenario in order to write a proper fix.
The .script file of the database follows a predefined order for the statements. This shouldn't be altered if it is edited and only certain manual changes are allowed (see the guide for details).
You can execute the ALTER TABLE statement via JDBC at the start of your test instead of inserting it in the script.
If IDENTITY values for the PRIMARY KEY collide, you will get an exception when you insert the values.
The actual fix for a problem like this is to RESTART WITH the max value in the primary key column plus one.
I think SEQUENCES are much more flexiblee than IDENTITY. The IDENTITY generator disabled JDBC batching, by the way.
But if you use SEQUENCE identifiers, you must pay attention to the hilo optimizers as well, because identifier are generated by Hibernate using a sequence value as a base calculation starting point.
With a SEQUENCE the restart goes like this:
ALTER SEQUENCE my_seqeunce RESTART WITH 105;

replace on duplicate with simpleJdbcInsert

I use tomcat for my server side with Mysql Server 5.5.
i use Spring framework for the database connectivity.
I would like to be able to insert a row to a table using simpleJdbcInsert. if the insert fails because of a duplicate i want it to replace the duplicated row.
is there a way to do that simpleJdbcInsert or should I just use jdbcTemplate and create my on query with "ON DUPLICATE" statement?
thanks
logically, duplication occurs when there is a double primary key.
so, if you are using the traditional JDBC or even Hibernate, then you should check if the same primary key value already exists, before you insert the new one.
but if the primary key is not set yet, or will be set by the DBMS, there will be different problem.
in JDBC, again you need to do the old style manual checking by querying before inserting the new one, but
in Hibernate, you just need to update it. Hibernate will create the new if there is no duplicate, but will replace if there is a duplicate,.
in JDBC, again you need to do the old style manual checking by querying before inserting the new one, but
That's incorrect - because there is a time gap between the checking of existing row and the actual insert, so somebody in parallel thread can insert the row with the same key simultaneously and you'll obtain the DuplicateKeyException (if we talk about spring). So, you have to handle this exception or just use the honest SQL insert into ... on duplicate key ....

Categories