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;
Related
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.
I am using SQLite in JAVA. I created a table with column id AUTOINCREMENT but I want to reset id after deletion so that no gaps exist between ids. How can I reset it ?
Although I highly recommend agains, like laalto
What problem would that solve? Generally, identifiers should stay stable. Having gaps between identifiers is not a problem.
It's possible by itinerating through all records with an auxiliary variable starting at 1 and incrementing. After reseting all id's you need to change the AUTOINCREMENT seed by running this SQL command DBCC CHECKIDENT('TABLE_NAME', RESEED, LATEST_RECORD)
EDIT
My bad here's the code for SQLite
UPDATE SQLITE_SEQUENCE SET SEQ=NEWSQUENCE WHERE NAME='table_name';
I'm using InnoDb schema on mysql 5.5.
mysql 5.5 guide states:
InnoDB uses the in-memory auto-increment counter as long as the server
runs. When the server is stopped and restarted, InnoDB reinitializes
the counter for each table for the first INSERT to the table, as
described earlier.
This is a big problem for me. I'm using envers to keep entities audits. I get as many errors as many "last rows" I delete.
Suppose I'm starting insert data into an empty table. Suppose to insert 10 rows. Then suppose to delete the last 8. In my table I will have as result 2 entities, with id 1 and 2 respectively. In the audit table I will have all 10 entities, with id from 1 to 10: entities with id from 3 to 10 will have 2 action: create action and delete action.
auto-increment counter is now setted to 11 in main table. Restarting mysql service auto-increment counter goes to 3. So if I insert a new entity, it will be saved with id 3. But in audit table there is already an entity with id = 3. That entity is already marked as created and deleted. It result in an assertion failure during update /delete action because envers cannot handle this inconsistent state.
ERROR org.hibernate.AssertionFailure - HHH000099: an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session): java.lang.RuntimeException: Cannot update previous revision for entity Customer_H and id **.
Is there a way to change this behaviour and keep auto-increment values while restarting server?
As I remember also in mysqldump generated file there is no infos about autoincrement counter. So also restoring a dump could be a problem!
An easy solution for this type of database is to utilize soft-deletes rather than physical deletes.
A soft delete is where you add a field to your table that acts as a status/indicator as to whether the row is considered "active" or "inactive". From this, you base all your queries to your table where the indicator column equals the "active" sentinel value, ignoring the inactive ones.
There are other options available, but this is simple and easy enough to implement.
The solution is to set the envers configuration property org.hibernate.envers.allow_identifier_reuse to true.
What it does is setting the deleted records revend* columns when a new entry with the same ID is being added. So you won't get entries where there are two records with the same ID and NULL revend.
There might still be an issue with current records where there is more than one record with NULL revend for the same ID. This issue, unfortunately, has to be handled manually. This means that you have to set the revend by yourself for those records leaving just one with NULL revend.
See also:
https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/Hibernate_User_Guide.html#envers-configuration
Just a quick question about locking tables in a postgres database using JDBC. I have a table for which I want to add a new record to, however, To do this for the primary key, I use an increasing integer value.
I want to be able to retrieve the max value of this column in Java and store it as a variable to be used as a new primary key when adding a new row.
This gives me a small problem, as this is going to be modelled as a multi-user system, what happens when 2 locations request the same max value? This will of course create a problem when trying to add the same primary key.
I realise that I should be using an EXCLUSIVE lock on the table to prevent reading or writing while getting the key and adding a new row. However, I can't seem to find any way to deal with table locking in JDBC, just standard transactions.
psuedo code as such:
primaryKey = "SELECT MAX(id) FROM table1;";
primary key++;
//id retrieved again from 2nd source
"INSERT INTO table1 (primaryKey, value 1, value 2);"
You're absolutely right, if two locations request at around the same time, you'll run into a race condition.
The way to handle this is to create a sequence in postgres and select the nextval as the primary key.
I don't know exactly what direction you're heading and how your handle your data, but you could also set the column as a serial and not even include the column in your insert query. The column will automatically auto increment.
I currently have a webservice which inserts information in a mysql database using Hibernate. Some of this information needs to be processed by another 'import' application. I would like to not have to trigger this application from the webservice. So the webservice doesn't have a dependency on the webservice and visa versa.
Is there a way to "listen" to changes (specifically: insert) in the database from the 'import' application and then start executing an action. I have looked at triggers but these seem to only work for changes in the application's Hibernate Session and not for 'external' changes.
Edit*
In short, the answer I would like to have;
Is it possible to monitor changes to a mysql database/table (coming from any source) from a java application which does not alter the database/table itself
Bounty Update*
I will award the bounty to the person who can explain to me how to monitor changes made to a MySQL table/database using a Java application. The Java application monitoring the changes is not the application applying any changes. The source of the alterations can be anything.
I think you could acheive something like this fairly easily, assuming you didn't mind a creating some extra tables & triggers on your database, and that the monitoring java application would have to poll the database rather than specifically receive triggers.
Assuming the table you're wanting to monitor is something like this:
CREATE TABLE ToMonitor ( id INTEGER PRIMARY KEY, value TEXT );
Then you create a table to track the changes, and a trigger that populates that table:
CREATE TABLE InsertedRecords( value TEXT );
CREATE TRIGGER trig AFTER INSERT ON account
FOR EACH ROW INSERT INTO InsertedRecords( value ) VALUES ( NEW.value );
This will cause the InsertedRecords table to be populated with every insert that happens in ToMonitor.
Then you just need to set up your monitoring app to periodically SELECT * from InsertedRecords, take the appropriate action and then clear out the records from InsertedRecords
EDIT: A slight alternative, if you didn't mind a bit of C/C++ coding, would be to follow the instructions here to create a custom SQL function that triggered your monitoring application into action, and then just call that SQL function from within the trigger you'd created.
You can read mysql binary log. Here you can find some information. There is a java parser and another one - but it is marked as unfinished) also you can look for similar parsers using another languages (for example, perl) and rewrite them in Java.
Also have a look at mysql-proxy.
I know it's not what you asked (thus, this is not a proper answer), but if you consider dropping the idea of "letting the DB notify the apps", you get the perfect case for using JMS for communication between apps.
Your app originating the change could publish a message to a JMS topic, which is subscribed by the second application. Once the first changes the database, it puts a message on the topic. The second then sees this new event and act accordingly. You could even publish the delta in the message, so that the second app don't need to reach the database.
I'm a bit against dealing with this by "hacking" the database to do more than just store data, as it will innevitably get into trouble in the future (as everything eventually will), and debugging it will be hard. Imagine adding a third app the the ecosystem, and you have now to replicate whatever you did for the second app, but now for the third app. If you didn't document your steps, you might get lost.
If you just use a JMS server between those two apps, you can certainly add a third app in the future, that just listens to this topic (and publish a new message, in case it has write access to the db), and the other apps don't even have to know that there's one more app out there. Nor the database.
assume we want to monitor changes in the table 'table1'
CREATE TABLE `table1` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`value` VARCHAR(50) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=10;
above is the query to create 'table1' it contain a 'id' column which is auto increment
create another table to store changes. Query is given below
CREATE TABLE `changes` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`changes` VARCHAR(200) NULL DEFAULT '0',
`change_time` TIMESTAMP NULL DEFAULT NULL,
`tablename` VARCHAR(50) NULL DEFAULT NULL,
`changed_id` VARCHAR(10) NULL DEFAULT NULL,
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=21;
now create a trigger in first table ie.. 'table1' Query is given below
delimiter |
create trigger trg_table1 AFTER INSERT ON table1
FOR EACH ROW BEGIN
DECLARE lastid INT DEFAULT 0;
SELECT max(id) INTO lastid from table1;
insert into changes values(null,'insert',now(),'table1',lastid);
end;
|
delimiter ;
Now if you try to insert anything in 'table1' its details will be automatically inserted in to changes table.
In changes table changes indicates the type of change ie.. insert,update etc
change_time indicates time at which change occur
tablename indicates table in which change occur
changed_id indicates id of the newly inserted row in the 'table1'
Now create a java program that continuously reading from 'changes' table.
A new entry in 'changes' table means something happened to database.
From each record in the 'changes' table you can understand in which table the insert operation is occured. And based on this you can perform appropriate action. After performing appropriate operation delete that row from 'changes' table.
You can create trigger (like i did above) for each table in your database...
From 'changes' table's 'tablename' column you can understand insert occurred in which table..
You could use a queuing solution like Q4M, but it might be overkill in your situation. But you could:
In the MySQL database, add a timestamp column to the table that is being inserted into. In the 'import' application either use a java.util.timer, or an external scheduler like cron. Use one of those to trigger a task that reads the insert table where the timestamp column is null. Take the appropriate action for those rows, and then set the timestamp column with a value. If there are no rows with a null time stamp, you have no new inserts. Simple, but it works.
You may want to add an index to the timestamp column for performance reasons.
I guess this answer is pretty late but as #dbf pointed out, reading binlog might be the way to go. I suggest looking into Debezium's MySQL Connector.
The Debezium MySQL connector reads the binlog and produces change
events for row-level INSERT, UPDATE, and DELETE operations and records
the change events in a Kafka topic.
Your "another" application can then read those Kafka topics.