How to Set null Into QueryDSL Timestamp Column During Update? - java

I have the following table in Postgre:
CREATE TABLE user_attempts
(
id INT PRIMARY KEY NOT NULL,
username VARCHAR(50) NOT NULL,
attempts SMALLINT NOT NULL,
lastmodified TIMESTAMP,
FOREIGN KEY ( username ) REFERENCES users ( username )
);
I would like to update the lastmodified field through QueryDSL and Spring Data JDBC Extension as follow:
template.update(userAttempts, update -> update
.set(userAttempts.attempts, (short) 0)
.set(userAttempts.lastmodified, null) // compilation error here
.where(userAttempts.username.eq(username))
.execute();
However, it seems that QueryDSL's set method can't accept null because it will match more than one function signatures.

Based on this QueryDSL issue: https://github.com/querydsl/querydsl/issues/846
I should use setNull method instead:
template.update(userAttempts, update -> update
.set(userAttempts.attempts, (short) 0)
.setNull(userAttempts.lastmodified)
.where(userAttempts.username.eq(username))
.execute()
);

You'll probably have to cast null to the type of lastmodified. E.g.:
.set(userAttempts.lastmodified, (Timestamp) null)
The reason for this is that the null literal is ambiguous when calling overloaded generic methods.

Related

ActiveJDBC: save() generates insert only for auto-generated primary key?

I recently started using ActiveJDBC. I have the following table (postgresql)
CREATE TABLE users (
id uuid PRIMARY KEY,
email text UNIQUE NOT NULL,
password text,
<some more nullable columns>
created_at timestamp with time zone NOT NULL default (now() at time zone 'utc'),
updated_at timestamp with time zone
);
As you can see, the primary key is of type uuid and has no auto-generate value of any kind.
This is my User class which models the table :
public class User extends Model
{
public User() {
setId(UUID.randomUUID()); // sets value to primary key
}
...
}
This is my attempt to create a new row and insert it :
User u = new User();
System.out.println(u.saveIt());
actually, I expected the insert to fail, since I did not set any value for mandatory email column. However, simply got false as return value. when I turned on logging, I saw that the framework generated an update sql instead of insert:
[main] INFO org.javalite.activejdbc.DB - Query: "UPDATE users SET email = ?, ..., updated_at = ? WHERE id = ?", with parameters: <null>, <null>, <null>, <null>, <null>, <null>, <null>, <2016-01-07 17:30:46.025>, <0621fbdb-5b95-4ee7-a474-8ee9165e2982>, took: 1 milliseconds
so I looked at the save() method inside org.javalite.activejdbc.Model class and saw this piece of code:
if (getId() == null) {
result = insert();
} else {
result = update();
}
does this mean that id column has to be empty in order for an insert to be generated ? if this is true this is unacceptable, so I must be missing something.
#sharonbn, please, see this documentation page: http://javalite.io/surrogate_primary_keys.
ActiveJDBC depends on autogenerated IDs. If the ID == null, the frameworks assumes this is a new record, and generates INSERT statement. If it is non-null, it is assumed the record already exists, and generates UPDATE.
In your case, you will need to explicitly call user.insert() instead of user.saveIt(). This is an 'expert' mode in cases when developers want to be in control of ID management. Further, the method user.update() is private. So for you to insert a new record, you will be calling
user.insert();
and for updates:
user.save();
// or:
user.saveIt();
, depending on what you want.

Updating rows of updatable view with ResultSet.updateRow function

I have updatable view on PostgreSQL server.
Update query works fine when I execute it from pgAnmin3 console, but when I try to update this view with ResultSet.updateRow() method, I get the following error:
org.postgresql.util.PSQLException: No primary key found for table
I guess I can't specify primary key for view.
Can I specify key columns for ResultSet.updateRow() method in my client application? Or can I specify a WHERE clause for ResultSet.updateRow() method?
Here are my tables
CREATE TABLE fin.t_year
(
id serial NOT NULL,
date_begin date NOT NULL,
date_end date NOT NULL,
year_name character varying(128),
CONSTRAINT "PK_year" PRIMARY KEY (id)
)
WITH (
OIDS=FALSE
);
CREATE TABLE fin.t_period
(
id serial NOT NULL,
id_year integer NOT NULL,
per_begin date NOT NULL,
per_end date NOT NULL,
per_name character varying(256),
CONSTRAINT "PK_period" PRIMARY KEY (id),
CONSTRAINT "FK_period_year" FOREIGN KEY (id_year)
REFERENCES fin.t_year (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
OIDS=FALSE
);
CREATE VIEW fin.vi_period AS
SELECT per.id,
per.per_begin AS "Begin",
per.per_end AS "End",
per.per_name AS "Name",
y.year_name AS "Year"
FROM fin.t_period per
JOIN fin.t_year y ON y.id = per.id_year;
CREATE OR REPLACE FUNCTION fin.tgfn_vi_period_update()
RETURNS trigger AS
$BODY$
DECLARE
id_row INTEGER;
id_curr INTEGER;
result RECORD;
BEGIN
id_curr = NEW.id;
-- Replace text identifier with integer primary key
IF NEW."Year" IS NOT NULL THEN
SELECT id INTO id_row
FROM fin.t_year
WHERE year_name = NEW."Year";
UPDATE fin.t_period SET id_year = id_row
WHERE id = id_curr;
END IF;
IF NEW."Begin" IS NOT NULL THEN
UPDATE fin.t_period SET per_begin = NEW."Begin"
WHERE id = id_curr;
END IF;
IF NEW."End" IS NOT NULL THEN
UPDATE fin.t_period SET per_end = NEW."End"
WHERE id = id_curr;
END IF;
IF NEW."Name" IS NOT NULL THEN
UPDATE fin.t_period SET per_name = NEW."Name"
WHERE id = id_curr;
END IF;
SELECT * INTO result FROM fin.vi_period WHERE id = id_curr;
RETURN result;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
This insert statement works fine
UPDATE fin.vi_period SET "Year" = 'new_year_name' WHERE id = 10;
But the problem with this java code
statement = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);
ResultSet rs = statement.ExecuteQuery("SELECT * FROM fin.vi_period;");
rs.absolute(pos + 1);
rs.updateString("new_year_name");
rs.updateRow();
The problem is that you are querying a view, and that view doesn't have a primary key (I am not sure if that is even possible with PostgreSQL, but most database don't support that). The JDBC driver requires a primary key to be able to make the result set updatable.
In other words: you cannot update this view through the result set. You either need to use an explicit UPDATE statement, or do this directly on the underlying table, not through the view.

JDBC Return generated key or existing key

I have a table with unique index to eliminate duplicates (simplified example)
CREATE TABLE `domain` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`subdomain` VARCHAR(255) NOT NULL,
`domain` VARCHAR(63) NOT NULL,
`zone` VARCHAR(63) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `UNIQUE` (`subdomain` ASC, `domain` ASC, `zone` ASC),
ENGINE = InnoDB;
I insert a lot of rows and i need to get primary keys returned (for other one-to-many inserts).
My problem is, that I insert a lot of duplicates and I need those keys returned too.
This is my solution which works, but isn't there more simple solution? With this I cannot use batch inserts and I want this to be most efficient.
PreparedStatement selectDomain = connection.prepareStatement("SELECT id FROM domain WHERE subdomain = ? AND domain = ? AND zone = ?");
PreparedStatement insertDomain = connection.prepareStatement("INSERT INTO domain(subdomain, domain, zone) VALUES (?,?,?)", Statement.RETURN_GENERATED_KEYS);
public int insertDomain(String subdomain, String domain, String zone) throws SQLException {
int domainId = 0;
selectDomain.setString(1, subdomain);
selectDomain.setString(2, domain);
selectDomain.setString(3, zone);
ResultSet resultSet = selectDomain.executeQuery();
if (resultSet.next()) {
domainId = resultSet.getInt(1);
} else {
insertDomain.setString(1, subdomain);
insertDomain.setString(2, subdomain);
insertDomain.setString(3, subdomain);
insertDomain.executeUpdate();
resultSet = insertDomain.getGeneratedKeys();
if (resultSet.next()) {
domainId = resultSet.getInt(1);
}
}
selectDomain.clearParameters();
insertDomain.clearParameters();
}
As I understand its not so easy approach for using batch execution. Your apporach is the best way to get the auto generated keys. There are few limitations of JDBC driver and it varies version to version, where getGeneratedKeys() works for single entry.
Please look into below links, it may help you :-
How to get generated keys from JDBC batch insert in Oracle?
http://docs.oracle.com/database/121/JJDBC/jdbcvers.htm#JJDBC28099
You could modify your INSERT to be something like this:
INSERT INTO domain (subdomain, domain, zone)
SELECT $subdomain, $domain, $zone
FROM domain
WHERE NOT EXISTS(
SELECT subdomain, domain, zone
FROM domain d
WHERE d.subdomain= $subdomain and d.domain=$domain and d.zone=$zone
)
LIMIT 1
Where $subdomain, $domain, $zone are the tag (properly quoted or as a placeholder of course) that you want to add if it isn't already there. This approach won't even trigger an INSERT (and the subsequent autoincrement wastage) if the tag is already there. You could probably come up with nicer SQL than that but the above should do the trick.
If your table is properly indexed then the extra SELECT for the existence check will be fast and the database is going to have to perform that check anyway.

MYSQL LAST_INSERT_ID not working as intended. How do I fix?

I wrote MySQL StoredProcedure to create and return new ID for each table value, however, it gets wrong value on last_insert_id() from MySQL WorkBench and Java application.
This procedure will be called from multiple sessions.
CALL `GET_NEW_ID`('test', #test);
select #test;
It gives me "141215000000" and this means last_insert_id() returns 0 all the time.
I see it correctly inserts new data into seq_data as supposed though.
CREATE PROCEDURE `GET_NEW_ID`(IN V_TABLE VARCHAR(10), OUT V_ID VARCHAR(12))
BEGIN
INSERT INTO seq_data ( id, `name`, `stat_date`)
SELECT IFNULL(MAX(id), 0)+1, V_TABLE, DATE_FORMAT(NOW(),'%Y%m%d') FROM seq_data WHERE name = V_TABLE AND stat_date = DATE_FORMAT(NOW(),'%Y%m%d');
SET V_ID = concat(DATE_FORMAT(NOW(),'%y%m%d'),LPAD(LAST_INSERT_ID(), 6, '0'));
END
Table looks like this.
CREATE TABLE `seq_data` (
`id` int(6) NOT NULL AUTO_INCREMENT,
`name` varchar(20) COLLATE utf8_bin NOT NULL,
`stat_date` varchar(8) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`id`,`name`,`stat_date`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
My goal is like...
CALL `GET_NEW_ID`('test', #test);
select #test;
return 141215000001
CALL `GET_NEW_ID`('test', #test);
select #test;
return 141215000002
CALL `GET_NEW_ID`('hello', #test);
select #test;
return 141215000001
As stated in the MySQL documentation, LAST_INSERT_ID() returns a BIGINT (64-bit) value representing the first automatically generated value that was set for an AUTO_INCREMENT column by the most recently executed INSERT statement to affect such a column.
In your case, you are inserting the id, so an AUTO_INCREMENT value is not generated, thus LAST_INSERT_ID returns 0.
You may try something like:
CREATE PROCEDURE `GET_NEW_ID`(IN V_TABLE VARCHAR(10), OUT V_ID VARCHAR(12))
BEGIN
INSERT INTO seq_data (`name`, `stat_date`)
SELECT V_TABLE, DATE_FORMAT(NOW(),'%Y%m%d') FROM seq_data WHERE name = V_TABLE AND stat_date = DATE_FORMAT(NOW(),'%Y%m%d');
SET V_ID = concat(DATE_FORMAT(NOW(),'%y%m%d'),LPAD(LAST_INSERT_ID(), 6, '0'));
END

Best way to initialize objects when using a dao

I'm building a Tomcat app using MySQL. I'm using a DAO pattern to talk to the database. I'm having trouble with where to initialize fields within the objects.
Mostly to save typing... I am using EclipseLink JPA to Generate model Entities from Tables. I am writing the DAO code by hand. I was writing the model by hand but this seemed easier.
The problem I am having is with writing the object back to the database will all the default nulls. For example, this table contains a bunch of id columns. Not all of them have context for every instance of the object. It seems inefficient to create the object and have to set all the fields to zero in order to be ale to save them to the database. If they have not context, I'd like to just leave them alone. i.e. I only want to set the fields that have context for what I am doing at that moment.
It seems best to use use the constructor in the model class to initialize them. But if I do that, EclipseLink will overwrite them the next time I generate the model.
Having the DAO update method check for nulls and set them to zero seems like a kludge. I suppose I could also use a factory to create and initialize the model class.
But I wonder if I am over thinking this... Any of these solutions would work. But there must be an accepted pattern for this.
How should I approach it?
Thanks
Model is just getters and setters. Contructor is empty.
Excerpts of code are below...
Notice notice = new Notice();
notice.setEvent("Welcome");
notice.setUserid(user.getId());
noticeDao.updateNotice(notice);
DAO:
//this seems inefficient
if(notice.getTravid() == null) notice.setTravid(0L);
if(notice.getBusid() == null) notice.setBusid(0L);
if(notice.getSaveid() == null) notice.setSaveid(0L);
if(notice.getTargid() == null) notice.setTargid(0L);
if(notice.getTestmode() == null) notice.setTestmode(false);
String SQLupdate = "UPDATE notices SET "
+ "userid = ?, "
+ "travid = ?, "
+ "busid = ?, "
+ "saveid = ?, "
+ "targid = ?, "
+ "testmode = ?, "
+ "event = ?, "
+ "status = ?, "
+ "error = ?, "
+ "created = ?, "
+ "modified = ?, "
+ "log = ? "
+ "WHERE id = ?";
ps = conn.prepareStatement(SQLupdate);
ps.setLong(1, notice.getUserid());
ps.setLong(2, notice.getTravid());
ps.setLong(3, notice.getBusid());
ps.setLong(4, notice.getSaveid());
ps.setLong(5, notice.getTargid());
ps.setBoolean(6, notice.getTestmode());
ps.setString( 7, notice.getEvent());
ps.setString( 8, notice.getStatus());
ps.setString( 9, notice.getError());
ps.setObject(10, notice.getCreated());
ps.setObject(11, notice.getModified());
ps.setString(12, notice.getLog());
ps.setLong( 13, notice.getId());
ps.executeUpdate();
DB:
CREATE TABLE `notices` (
`id` int(20) unsigned NOT NULL AUTO_INCREMENT,
`userid` int(20) unsigned NOT NULL DEFAULT '0',
`travid` int(20) unsigned NOT NULL DEFAULT '0',
`busid` int(20) unsigned NOT NULL DEFAULT '0',
`saveid` int(20) unsigned NOT NULL DEFAULT '0',
`targid` int(20) unsigned NOT NULL DEFAULT '0',
`testmode` tinyint(1) DEFAULT '0',
`event` varchar(40) DEFAULT NULL,
`status` varchar(20) DEFAULT 'Pending',
`error` varchar(255) DEFAULT NULL,
`created` datetime DEFAULT NULL,
`modified` datetime DEFAULT NULL,
`log` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=49 DEFAULT CHARSET=utf8
The database table is like this:
ID
Generically, the code looks like this:
Just to address your specific question, isn't there a way to tell eclipselink to set default values of the fields?
But there may be a deeper problem, with your database design. Are the *id fields not foreign key? They should be. And if they are foreign keys, then their value in database should be null, not 0L for rows where the specific fields do not have context.
At even deeper lever, if most of them don't have context in most rows -- that is, not being in context is an exception rather then norm -- then perhaps your database design itself is not good. You might be designing a universal table and will benefit from breaking the single table into multiple.
Thanks for the great input. The solution I settled on was just to use the generic setObject for all data types. i.e.
ps.setLong(1, notice.getUserid());
becomes
ps.setObject(1, notice.getUserid());
MySQL seems perfectly happy with that. NULLs are not a problem. And it's a lot less work.

Categories

Resources