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.
Related
I want to embed date information in the primary key, for a table that will be partitioned (monthly) in a PostgreSQL database. This should in theory speed up the process on finding out in which partition to look for the data. I followed this article to embed the date in a date into the serial.
Now, I am however facing the problem that I can't get the Id been used by Hibernate.
c.f. the sql that should give an idea of the attempted approach.
CREATE SEQUENCE test_serial START 1;
CREATE OR REPLACE FUNCTION gen_test_key() RETURNS BIGINT AS $$
DECLARE
new_id bigint;
BEGIN
new_id = (nextval('public.test_serial'::regclass)::bigint % 10000000000000::bigint
+ ( (EXTRACT(year from now())-2000)::bigint * 10000::bigint
+ EXTRACT(month from now())::bigint * 100::bigint
+ EXTRACT(day from now())::bigint
)::bigint * 10000000000000::bigint
)::bigint;
RETURN new_id;
END;
$$ LANGUAGE plpgsql;
CREATE TABLE test
( id bigint primary key default gen_test_key(),
something text,
tstamp timestamp default now()
) PARTITION BY RANGE (id);
CREATE TABLE test_2022_10 PARTITION OF test
FOR VALUES FROM (2210100000000000000::bigint ) TO (2211010000000000000::bigint);
I came across a similar question, where it was suggested to use a stored procedure. Unfortunately only functions are allowed as default in the table definition and therefore stored procedures, seam not to work for me.
I think what you need here is a subtype of SequenceStyleGenerator that overrides determineBulkInsertionIdentifierGenerationSelectFragment to run the code of this function. You should be able to configure this generator on your entity with #GenericGenerator. I understand the desire to use this concept when you don't want to change your existing queries, but are you sure that partitioning will help you in your use case?
Also, be careful and do not rely on the date information in the primary key, because with pooled optimizers, it might happen that a value is generated way before it actually is used as primary key for a row.
So this is a solution that worked out in the end as suggested #ChristianBeikov here the entity with the annotations pointing to the CustomIdGenerator.
public class Test {
#Id
#GenericGenerator(name = "CustomIdGenerator", strategy = "nl.test.components.CustomIdGenerator")
#GeneratedValue(generator = "CustomIdGenerator")
private Long id;
private String something;
private OffsetDateTime tstamp;
}
As explained by #Mr_Thorynque it is similarly possible to call a stored function as a procedure. Just replace "CALL gen_test_key()" with "SELECT gen_test_key()" and don't pass it to the wrong method for stored procedures connection.prepareCall(CALL_STORE_PROC);, but instead connection.prepareStatement(STORED_FUNCTION); So, this is the CustomIdGenerator.
public class CustomIdGenerator implements IdentifierGenerator {
private static final String STORED_FUNCTION = "select gen_test_key()";
#Override
public Serializable generate(SharedSessionContractImplementor session, Object object) throws HibernateException {
Long result = null;
try {
Connection connection = session.connection();
PreparedStatement pstmt = connection.prepareStatement(STORED_FUNCTION);
ResultSet resultSet = pstmt.executeQuery();
if (resultSet.next()) {
result = resultSet.getLong(1);
System.out.println("Generated Id: " + result);
}
} catch (SQLException sqlException) {
throw new HibernateException(sqlException);
}
return result;
}
}
Go easy on me, middle school teacher taking a CS class. I've got a Java program that asks for user name, height, weight, does some calculations and gives results to the user. I now need to store this data in a database. I can get the data to store until I start using primary and foreign keys.
Here is the error I can't figure out:
Error: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL180429151131780' defined on 'USERPROFILE'.
Here is my table:
drop table stayfitapp.userdata;
drop table stayfitapp.userprofile;
drop schema stayfitapp restrict;
create schema stayfitapp;
create table stayfitapp.userprofile
(
profileName varchar(255) not null primary key,
profileGender varchar(255) not null
);
create table stayfitapp.userdata
(
profileAge double not null,
profileWeight double not null,
profileHeight double not null,
profileWaistCircumference double not null,
profileHipCircumference double not null,
profileName varchar(255),
foreign key (profileName) references stayfitapp.userprofile(profileName)
);
Here is the section of the "app" that writes to the table...
public void save(){
try {
String query = "insert into stayfitapp.userprofile" + "(profileName, profileGender)" + "values" + "(?,?)";
String query2 = "insert into stayfitapp.userdata" + "(profileAge, profileWeight, profileHeight, profileWaistCircumference, profileHipCircumference)" + "values" + "(?,?,?,?,?)";
Connection myConnection = DriverManager.getConnection("jdbc:derby://localhost:1527/stayfitDB2", "username", "password");
Statement myStatement = myConnection.createStatement();
//Statement myStatement2 = myConnection.createStatement();
PreparedStatement prepared = myConnection.prepareStatement(query);
prepared.setString(1, profileName);
prepared.setString(2, profileGender);
PreparedStatement prepared2 = myConnection.prepareStatement(query2);
prepared2.setDouble(1, profileAge);
prepared2.setDouble(2, profileWeight);
prepared2.setDouble(3, profileHeight);
prepared2.setDouble(4, profileWaistCircumference);
prepared2.setDouble(5, profileHipCircumference);
int rowsAffected = prepared.executeUpdate();
int rowsAffected2 = prepared2.executeUpdate();
if(rowsAffected==0)
{
System.out.println("Warning: User data did not save!");
}
else
{
System.out.println("User info saved!");
}
}
catch(SQLException e)
{
System.out.println("Error: "+e.toString());
}
Your save() method will attempt to add the user to the stayfitapp.userprofile table. This table has a field called profileName. profileName is the "primary key" so no duplicate values are allowed.
The error that you are getting is saying that you cannot add(insert) the record to the table because the table already has a record with the same name.
Does your program work okay if you use a different name each time?
You will need to add some logic to your program to deal with the scenario where the profileName already exists in the table. This will probably involve deleting or updating the existing record.
This is the problem.
insert into stayfitapp.userprofile"
+ "(profileName, profileGender)" + "values" , etc
You have nothing to check to see if a record already exists. Something like this would work better.
insert into stayfitapp.userprofile
profileName, profileGender
select distinct ?, ?
from someSmallTable
where not exists (
select 1
from stayfitapp.userprofile
where profileName = ?
)
The someSmallTable bit depends on your database engine, which you didn't specify.
I ended up writing a method to check if the username was already in the profile table. If the username was a duplicate I only wrote to the data table. If the username was new I wrote to both tables.
Thank you for your help! I'm sure there was a more efficient method (figuratively and literally) but I'm on to my final project and nearly surviving an actual CS class.
I need to use the database Firebird and for this I use the Jaybird 2.2.9.
When I used the MySQL driver, to converter of ResultSet to Object this way:
empresa.setBairro(rs.getString("empresa.bairro")); // (Table.Column)
empresa.setCep(rs.getString("empresa.cep")); // (Table.Column)
empresa.setCidade(rs.getString("empresa.cidade")); // (Table.Column)
But with Jaybird the resultSet don't return rs.getString("Table.Column")
I need this way when I have inner join in SQL.
Anyone help me?
This is my full code
public ContaLivros converterContaLivros(ResultSet rs, Integer linha) throws Exception {
if (rs.first()) {
rs.absolute(linha);
ContaLivros obj = new ContaLivros();
obj.setId(rs.getLong("cad_conta.auto_id"));
obj.setNome(rs.getString("cad_conta.nome"));
if (contain("cad_banco.auto_id", rs)) {
obj.setBancoLivros(converterBancoLivros(rs, linha));
} else {
obj.setBancoLivros(new BancoLivros(rs.getLong("cad_conta.banco"), null, null, null));
}
obj.setAgencia(rs.getInt("cad_conta.agencia"));
obj.setAgenciaDigito(rs.getInt("cad_conta.agencia_digito"));
obj.setConta(rs.getInt("cad_conta.conta"));
obj.setContaDigito(rs.getInt("cad_conta.conta_digito"));
obj.setLimite(rs.getDouble("cad_conta.limite"));
obj.setAtivo(rs.getString("cad_conta.ativo"));
return obj;
} else {
return null;
}
}
You can't. Jaybird retrieves the columns by its label as specified in JDBC 4.2, section 15.2.3. In Firebird the column label is either the original column name, or the AS alias, the table name isn't part of this. The extension of MySQL that you can prefix the table name for disambiguation is non-standard.
Your options are to specify aliases in the query and retrieve by this aliasname, or to process the result set metadata to find the right indexes for each column and retrieve by index instead.
However note that in certain queries (for example UNION), the ResultSetMetaData.getTableName cannot return the table name, as Firebird doesn't "know" it (as you could be applying a UNION to selects from different tables).
The name in jdbc will not have the table in it.
You can either
work with positional parameters ( getString (1); and so on )
Or
define column name alias in your select (select a.name namefroma from tableone a )
Or
simply do rs.getString ("column"); without the table prefix if name is unambigous
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.
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.