Get Current User for Delete Trigger - Spring MyBatis - Liquibase - java

I have a Spring services project that uses MyBatis and Liquibase.
I've made an audit table that has triggers for INSERT/UPDATE/DELETE.
With INSERT/UPDATE I'm already storing the user id so it's not a problem to do NEW.USER_ID, but with DELETE I only have OLD.USER_ID which obviously doesn't reflect the current user making the change.
Excluding some info, I have this in liquibase (putting *s around what should change):
<sql endDelimiter="|">
CREATE TRIGGER DELETE_TRIGGER
AFTER DELETE
ON TABLE_NAME
FOR EACH ROW
BEGIN
INSERT INTO TABLE_NAME_A (CHANGE_TYPE, CHANGE_ID, CHANGE_DATE)
VALUES ('DELETE', **OLD.USER_ID**, now());
END;
|
</sql>
So I'm not sure what to replace OLD.UPDATE_ID with.
The other examples I found often have to do with sql servers and mssql. So maybe I just failed as searching as I didn't find something that could work within spring/mybatis/liquibase/mysql.

Filling out how I solved this.
I changed the base trigger to be
<sql endDelimiter="|">
CREATE TRIGGER DELETE_TRIGGER
AFTER DELETE
ON TABLE_NAME
FOR EACH ROW
BEGIN
INSERT INTO TABLE_NAME_A (CHANGE_TYPE, CHANGE_ID, CHANGE_DATE)
VALUES ('DELETE', user(), now());
END;
|
</sql>
So that it fills the user field with something. Then after the deletion I wrote another mapper to go in and update the ID field to the current user calling my service.
<update id="updateAuditTableChangeIdAfterDeletion">
UPDATE TABLE_NAME_A
SET CHANGE_ID = #{1}
WHERE UNIQUE_IDENTIFIER = #{0}
AND CHANGE_TYPE = 'DELETE'
</update>

Related

I need to update the id along with the name and desc - SQL

I am selecting the id using
SELECT SCRTY_RISK_AREA_UI_AREA_ID_SQ.nextval as\"riskAreaNo\" FROM DUAL
This sql query from the database.
Now I need to update the name and description of the already existing data along with the id
UPDATE SCRTY_RISK_AREA
SET AREA_NAME= :areaName, AREA_DESC= :areaDesc,IS_ACTIVE='N'
WHERE UI_AREA_ID = :uiareaId");
Am updating using the above query.
Can anybody help me how to insert or generate the new id. am new to sql not sure how to generate new id
I am not sure how to update along with the id since i generate the id using sequence
If you're inserting a new row, then
insert into scrty_risk_area (id, area_name) values
(scrty_risk_area_ui_area_id_sq.nextval, :areaName);
If you're updating existing ID values (probably not a good idea, especially if it is a primary key, not to mention if it is referenced by some foreign keys):
update scrty_risk_area set
id = scrty_risk_area_ui_area_id_sq.nextval;

How should i pass a userId(or other parameter) to PSQL trigger AFTER DELETE?

I want to pass a parameter into a trigger function, which invokes AFTER DELETE of entity record in my database. Is there any way to do it on application-level? Is there a solution using "SET" operation? Or maybe I can add a parameter to the same transaction, which is used on delete operation?
You have a couple of choices here.
1. You can set the application_name environment variable to the user name, and then retrieve it in the trigger.
2. You can expand the table by a column, and send the user name to the database in the insert/update/delete statements.
SET application_name = 'user name';
-- in the trigger:
SELECT application_name FROM pg_stat_activity WHERE pid = pg_backend_pid();
or
ALTER TABLE t ADD COLUMN user_name text;
-- and in the trigger (depending on context):
my_user := NEW.user_name;
or
my_user := OLD.user_name;
I found another solution to this problem. My approach is the following:
I'm passing the username in the delete query as follows:
DELETE FROM products_images
WHERE id = #{id}
OR (SELECT false WHERE 'xusername' = '${xusername}')
Then at the trigger level, I'm extracting the username value using:
(SELECT SUBSTRING(current_query(), '''xusername'' = ''(.*)'''))
CREATE OR REPLACE FUNCTION version_product_image()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'DELETE' THEN
INSERT INTO versioning.products_images (id,product_id,relative_location,is_main,creation_time,username,action)
VALUES(OLD.id,OLD.product_id,OLD.relative_location,OLD.is_main,CURRENT_TIMESTAMP,(SELECT SUBSTRING(current_query(), '''xusername'' = ''(.*)''')),TG_OP);
ELSE
INSERT INTO versioning.products_images (id,product_id,relative_location,is_main,creation_time,username,action)
VALUES(NEW.id,NEW.product_id,NEW.relative_location,NEW.is_main,CURRENT_TIMESTAMP,NEW.username,TG_OP);
END IF;
RETURN NEW;
END;
$$
LANGUAGE 'plpgsql';
Note that in order for this to work, you have to pass the username as value, not as parameter.
Also note that I've only tested this with direct delete queries. Haven't tested it yet with cascades.

Spring JDBC Template batchUpdate to update thousands of records in a tbale

I have an update query which I am trying to execute through batchUpdate method of spring jdbc template. This update query can potentially match 1000s of rows in EVENT_DYNAMIC_ATTRIBUTE table which needs to be get updated. Will updating thousands of rows in a table cause any issue in production database apart from timeout? like, will it crash database or slowdown the performance of entire database engine for other connections...etc?
Is there a better way to achieve this instead of firing single update query in spring JDBC template or JPA? I have the following settings for jdbc template.
this.jdbc = new JdbcTemplate(ds);
jdbc.setFetchSize(1000);
jdbc.setQueryTimeout(0); // zero means there is no limit
The update query:
UPDATE EVENT_DYNAMIC_ATTRIBUTE eda
SET eda.ATTRIBUTE_VALUE = 'claim',
eda.LAST_UPDATED_DATE = SYSDATE,
eda.LAST_UPDATED_BY = 'superUsers'
WHERE eda.DYNAMIC_ATTRIBUTE_NAME_ID = 4002
AND eda.EVENT_ID IN
(WITH category_data
AS ( SELECT c.CATEGORY_ID
FROM CATEGORY c
START WITH CATEGORY_ID = 495984
CONNECT BY PARENT_ID = PRIOR CATEGORY_ID)
SELECT event_id
FROM event e
WHERE EXISTS
(SELECT 't'
FROM category_data cd
WHERE cd.CATEGORY_ID = e.PRIMARY_CATEGORY_ID))
If it is one time thing, I normally first select the records which needs to be updated and put in a temporary table or in a csv, and I make sure that I save primary key of those records in a table or in a csv. Then I read records in batches from temporary table or csv, and do the update in the table using the primary key. This way tables are not locked for a long time and you can have fixed set of records added in the batch which needs update and updates are done using primary key so it will be very fast. And if any update fails then you know which records got failed by logging out the failed records primary key in a log file or in an error table. I have followed this approach many time for updating millions of records in the PROD database, as it is very safe approach.

JDBC - PostgreSQL - batch insert + unique index

I have a table with unique constraint on some field. I need to insert a large number of records in this table. To make it faster I'm using batch update with JDBC (driver version is 8.3-603).
Is there a way to do the following:
every batch execute I need to write into the table all the records from the batch that don't violate the unique index;
every batch execute I need to receive the records from the batch that were not inserted into DB, so I could save "wrong" records
?
The most efficient way of doing this would be something like this:
create a staging table with the same structure as the target table but without the unique constraint
batch insert all rows into that staging table. The most efficient way is to use copy or use the CopyManager (although I don't know if that is already supported in your ancient driver version.
Once that is done you copy the valid rows into the target table:
insert into target_table(id, col_1, col_2)
select id, col_1, col_2
from staging_table
where not exists (select *
from target_table
where target_table.id = staging_table.id);
Note that the above is not concurrency safe! If other processes do the same thing you might still get unique key violations. To prevent that you need to lock the target table.
If you want to remove the copied rows, you could do that using a writeable CTE:
with inserted as (
insert into target_table(id, col_1, col_2)
select id, col_1, col_2
from staging_table
where not exists (select *
from target_table
where target_table.id = staging_table.id)
returning staging_table.id;
)
delete from staging_table
where id in (select id from inserted);
A (non-unique) index on the staging_table.id should help for the performance.

Create a oracle db trigger using thin jdbc driver

currently I setting up a test environment for an application. I'm using jUnit and Spring in my test environment. Before a test execution I want to set up a database test environment state. I already has written the SQL scripts (schema and data) and they runs fine in Oracles SQLDeveloper. As I tried to execute them by using the oracle thin jdbc driver, the execution fails. It looks like that the thin driver doesn't like create trigger statements.
I read that I have to use an oci driver instead of thin driver. The problem with the oci driver is that it is not platform independent and it takes time to set it up.
Example of my code:
CREATE TABLE "USER"
(
USER_ID NUMBER(10) NOT NULL,
CREATOR_USER_FK NUMBER(10) NOT NULL,
...
PRIMARY KEY (USER_ID)
);
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1;
CREATE TRIGGER "USER_ID_SEQ_INC" BEFORE
INSERT ON "USER" FOR EACH ROW BEGIN
SELECT SEQ_USER.nextval
INTO :new.USER_ID
FROM DUAL;
END;
If I execute the the trigger statement the execution fails, but I looks like that the first part of the query (CREATE TRIGGER "USER_ID_SEQ_INC" ... "USER" ... BEGIN ... FROM DUAL;) is executed successfully, but the trigger seems to be corrupt if I try to use it. The execution fail error comes with the second part of the statement END; "ORA-00900: invalid SQL statement".
Do anyone know a solution for that problem? I just want to create a trigger with platform independent thin jdbc driver.
Cheers!
Kevin
Thank you guys for your answers, It works fine now. The reason was a syntax mistake or the interpretation of my SQL code file with Spring Framefork. When I execute the statements directly by using the execute method of jdbc it works, when I use the Spring functionality for script execution the execution fails. With oracle sql code it seems to be tricky, because if I use hsqldb sql code it works fine.
test-condext.xml:
...
<jdbc:initialize-database data-source="dataSource"
ignore-failures="DROPS" enabled="${jdbc.enableSqlScripts}">
<jdbc:script location="${jdbc.initLocation}" />
<jdbc:script location="${jdbc.dataLocation}" />
</jdbc:initialize-database>
...
schema.sql:
DROP SEQUENCE SEQ_USER;
DROP TABLE "USER" CASCADE CONSTRAINTS;
PURGE TABLE "USER";
CREATE TABLE "USER"
(
USER_ID NUMBER(10) NOT NULL,
CREATOR_USER_FK NUMBER(10) NOT NULL,
PRIMARY KEY (USER_ID)
);
ALTER TABLE "USER" ADD CONSTRAINT FK_USER_CUSER FOREIGN KEY (CREATOR_USER_FK) REFERENCES "USER" (USER_ID);
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1;
CREATE TRIGGER "USER_ID_SEQ_INC" BEFORE
INSERT ON "USER" FOR EACH ROW
WHEN (new.USER_ID IS NULL)
BEGIN
SELECT SEQ_USER.nextval
INTO :new.USER_ID
FROM DUAL;
END;
/
ALTER TRIGGER "USER_ID_SEQ_INC" ENABLE;
This works fine! Its important to remove ; at the end of statements excepts the trigger statement!!!
#Before
public void executeSomeSql() {
Connection c;
try {
c = dataSource.getConnection();
c.createStatement()
.execute("CREATE TABLE \"USER\" (USER_ID NUMBER(10) NOT NULL, CREATOR_USER_FK NUMBER(10) NOT NULL, PRIMARY KEY (USER_ID))");
c.createStatement()
.execute("CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1");
c.createStatement()
.execute("CREATE OR REPLACE TRIGGER \"USER_ID_SEQ_INC\" BEFORE INSERT ON \"USER\" FOR EACH ROW WHEN (new.USER_ID IS NULL) BEGIN SELECT SEQ_USER.nextval INTO :new.USER_ID FROM DUAL; END;");
} catch (SQLException e) {
logger.debug(e);
}
}
Creating triggers works with any type of JDBC driver; there must be something wrong with the SQL syntax -- which is odd because Oracle should report that when you run the CREATE TRIGGER (not when you use it the first time).
Since you use BEGIN ... END; make sure that you really have a ; after END in the SQL which you send to the DB.
If that isn't the cause, check this article.
I know this is a old post but here's my answer.
By default, Spring "initialize-database" instruction split the specified script by using the semicolon character : ";".
In a trigger, there often is a semicolon inside the trigger, thus the queries are badly splitted and executed.
The solution is to use another split character ("|" for example) like this :
<jdbc:initialize-database>
<jdbc:script location="classpath:myscript.sql" separator="|"/>
</jdbc:initialize-database>

Categories