java + SQLite project. Foreign key "On Update" not updating - java

I am making a javafx (intelliJ with java jdk 11) app using SQLite version 3.30.1 with DB Browser for SQLite.
I have a table called "beehives" and each beehive can have diseases (stored in the table "diseases").
this is my "beehives" table:
CREATE TABLE "beehives" (
"number" INTEGER NOT NULL,
"id_apiary" INTEGER NOT NULL DEFAULT -2,
"date" DATE,
"type" TEXT,
"favorite" BOOLEAN DEFAULT 'false',
PRIMARY KEY("number","id_apiary"),
FOREIGN KEY("id_apiary") REFERENCES "apiaries"("id") ON DELETE SET NULL
);
this is my "diseases" table:
CREATE TABLE "diseases" (
"id" INTEGER NOT NULL,
"id_beehive" INTEGER NOT NULL,
"id_apiary" INTEGER NOT NULL,
"disease" TEXT NOT NULL,
"treatment" TEXT NOT NULL,
"start_treat_date" DATE NOT NULL,
"end_treat_date" DATE,
PRIMARY KEY("id"),
FOREIGN KEY("id_beehive","id_apiary") REFERENCES "beehives"("number","id_apiary") ON UPDATE CASCADE
);
this is my "apiaries" table in case you need it:
CREATE TABLE "apiaries" (
"id" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"address" TEXT,
PRIMARY KEY("id")
);
Everything works fine, but when I update a beehive (for example when I update the "number", which is the primary key in beehives table) the diseases does not update the number. The result is that the diseases get some kind of disconnected since the beehive change his "number" correctly, but the disease doesn't update it. There is no error message.
My java method that calls the update is:
public void updateBeehiveInDB(Beehives newBeehive,Beehives oldBeehive){
try {
s = "UPDATE beehives SET number=?, id_apiary=?, date=?, type=?, favorite=? WHERE number=? and id_apiary=? ";
preparedStatement = connection.prepareStatement(s);
preparedStatement.setInt(1, newBeehive.getNumber());
preparedStatement.setInt(2, newBeehive.getId_apiary());
preparedStatement.setDate(3, newBeehive.getDate());
preparedStatement.setString(4, newBeehive.getType());
preparedStatement.setBoolean(5, newBeehive.isFavorite());
preparedStatement.setInt(6, oldBeehive.getNumber());
preparedStatement.setInt(7,oldBeehive.getId_apiary());
int i = preparedStatement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
I tried to check if foreign keys are "on" following the SQLite documentation here, but my English is not good enough and I am using DB Manager. So no idea how to check if this is on, or how to turn it on manually.
What can I do to update the diseases "id_beehives" when I update "number" on beehives table?

The problem was that i am using a composite foreign key and i need to implement it correctly on other tables too even if i was not using them yet in this new project. Was very hard to find the problem because intellij normally show all the SQL error messages, but in this case, it was not showing anything. But when i tried to do the SQL sentence manually in the DB Browser, there i got an error message and was able to fix it.
Also had to activate foreign key on the connection:
public Connection openConnection() {
try {
String dbPath = "jdbc:sqlite:resources/db/datab.db";
Class.forName("org.sqlite.JDBC");
SQLiteConfig config = new SQLiteConfig();
config.enforceForeignKeys(true);
connection = DriverManager.getConnection(dbPath,config.toProperties());
return connection;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}

Related

Error on swing aplication when adding a foreign key through combo box

I am having a problem on an aplication that is suposed to save a carrer on a table trough a swing form, but i keep getting this error that says that I can not add to a child row, and I know that it can be caused by the value added to the child row not existing where the PK should go, but I know for certain that there is a PK of that value at the parent table, so I do not know what could be happening.
The value that I am sending to the FK is the index of a combo box
These are the two tables:
create table facultad(
codigoFacultad int primary key auto_increment,
nombre varchar(50),
telefono varchar(50)
);
create table carrera(
codigoCarrera int primary key auto_increment,
nombre varchar(50),
cantidadMaterias int,
codigoFacultad int,
foreign key (codigoFacultad) references facultad(codigoFacultad) on update cascade
);
This is the procedure that i am using to save on the database
create procedure insertarCarrera(in idCar int, nom nvarchar(75), numMater int, codFac int)
insert into carrera values(idCar, nom, numMater,codFac);
this is how i run the save from swing:
public void insertarCarrera(Carrera ca){
try
{
this.conectar();
CallableStatement cal = this.getCon().prepareCall("{call insertarCarrera(?,?,?,?)}");
cal.setInt(1, ca.getCodigoCarrera());
cal.setString(2, ca.getNombre());
cal.setInt(3, ca.getCantidadMaterias());
cal.setInt(4, ca.getCodigoCarrera());
cal.executeUpdate();
JOptionPane.showMessageDialog(null, "Exito al insertar ");
} catch (SQLException e)
{
JOptionPane.showMessageDialog(null, "Error al insertar "+e.toString());
}
finally
{
this.desconectar();
}
}
conectar() is the method that calls the mySQL connection and ca is the object that i am filling with the data
and finaly this is how the data is being collected from the form
private void agregarCarrera() {
ca.setCodigoCarrera(Integer.parseInt(this.jTxtCodigo.getText()));
ca.setNombre(this.jTxtNombre.getText());
ca.setCantidadMaterias(Integer.parseInt(this.jSpinMaterias.getValue().toString() ));
ca.setCodigoFacultad(jCmbFacultad.getSelectedIndex());
dCa.insertarCarrera(ca);
}
hope somebody can help, been strugling the whole day due to this

Preventing from Multiple primary key error

This is my code for executing in my java program:
public static void createBooksTablesAndSetPK() {
String selectDB = "Use lib8";
String createBooksTable = "Create table IF NOT EXISTS books (ID int,Name varchar(20),ISBN varchar(10),Date date )";
String bookTablePK = "ALTER TABLE BOOKS ADD PRIMARY KEY(id)";
Statement st = null;
try (
Connection con = DriverManager.getConnection(dbUrl, "root", "2323");) {
st = con.createStatement();
st.execute(selectDB);
st.execute(createBooksTable);
st.execute(bookTablePK);
} catch (SQLException sqle) {
sqle.printStackTrace();
}
}
I cat use IF NOT EXIST for creating databasesand tables to prevent creating duplicate database and tables and corresponding errors.
But i don't know how prevent Multiple primary key error, because program may call createBooksTablesAndSetPK() multiple times.
Error:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Multiple primary key defined
The column Book_id is not existing in your case. You are creating a table with ID as the column and then updating the table with a PRIMARY KEY constraint on a column that is not existing.
Create table IF NOT EXISTS books (ID int,Name varchar(20),ISBN varchar(10),Date date )
ALTER TABLE BOOKS ADD PRIMARY KEY(BOOK_id)
Try running these statements on a MySQL command prompt (or MySql Workbench) and the see the error.
You need change the alter table command like this.
ALTER TABLE BOOKS ADD BOOK_id VARCHAR( 255 ), ADD PRIMARY KEY(BOOK_id);

MySQL Transactional delete and Java

I've got tables like this:
Table A:
`id | name`
Table B:
`id | A_id | ....`
A_id is a foreign key to Table A, the Engine is InnoDB
This is the code that fails:
String[] cleanupQueries = new String[] { "DELETE FROM B WHERE A_id = (SELECT id FROM A WHERE name = 'test')",
"DELETE FROM A WHERE name = 'test'" };
Connection connection;
try {
connection = DriverManager.getConnection(getConnectionString());
connection.setAutoCommit(false);
} catch (SQLException e) {
throw new RuntimeException("Error establishing a database connection!");
}
try {
for(String cleanupQuery : cleanupQueries) {
PreparedStatement statement = connection.prepareStatement(cleanupQuery);
statement.executeUpdate(); //FAILS WHEN EXECUTING THE SECOND QUERY
}
} catch(SQLException e) {
throw new RuntimeException("Error while executing the queries in the transactional context!");
}
try {
connection.commit();
} catch (SQLException e) {
rollback(connection);
throw new RuntimeException("Error while comitting!");
}
The Exception i get is:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails ('DATABASE/TABLE', CONSTRAINT 'FK_B_A' FOREIGN KEY ('FK_A') REFERENCES 'A' ('ID') ON DEL)
The database doesn't let me delete A while there are still B's left, but the first query deleted all B's. I want to delete all B's and the A they reference only completely.
I don't want to change the Tables to have cascading deletes. What shall i do to get the code working?
Cause for error is
The Foreign Key has referenced to the table A id so if you would like to delete the F_Key , first you should delete the Child references values of that foreign keys then only its possible to delete the parent.
Correct me if 'm wrong..
Simply add the cascade is true while deleting the foreign key constraint.The child table entry is automatically deleted when you delete the original parent entry.
Try:
"DELETE FROM B WHERE A_id = (SELECT id FROM A WHERE name IN 'test')"
Since the child rows are deleted in the same transaction, the deleted rows are still visible and thus the parent rows could not be deleted.
This may be because of the transaction isolation setting on the connection. I would try different levels and see which one allows it.

Cascade not working with Java

I've a SQLite db with 2 tables: Quotes and WPList
the two tables have this column:
Quotes: ID (int primary key), name (String)
WPList: ID (int primary key), ID_Quote (foreign key Quotes with cascade option), String name
The problem is that if I execute query with an external tool (I use Navicat Essential for Mac) the CASCADE works correclty and if I delete a Quote all entry in WPList with his ID are eliminated. With Java this does not happen, the CASCADE does not work. any suggestion? thanks
EDIT.
This is the java code I use:
public static void deleteQuote (int ID)
{
try
{
Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection(Global.dbPath);
Statement stmt;
stmt = conn.createStatement();
stmt.execute("DELETE FROM Quotes WHERE ID=" + ID);
stmt.close(); // rilascio le risorse
conn.close(); // termino la connessione
}
catch(ClassNotFoundException e)
{
System.out.println(e);
}
catch(SQLException e)
{
System.out.println(e);
}
}
Are you winding up with a different version of the SQLite library in your two cases? Per the FAQ, SQLite supports enforcement of foreign key constraints as of version 3.6.19.
Provided that your SQLite library is compiled with support for foreign keys, you still must enable this feature with a pragma statement, per the instructions in the "Foreign Key Support" documentation. You need to evaluate the following statement:
PRAGMA foreign_keys = ON;
My guess is that your Navicat Essential tool is enabling foreign key support, but your Java code did not do the same.

Java Swing & Postgres user authentication: Close old connection when new connection opened

I have a Java Swing application that accesses a Postgres database using a simple Singleton Pattern:
public class DatabaseConnection {
private static final String uname = "*******";
private static final String pword = "*******";
private static final String url = "*******************************";
Connection connection;
// load jdbc driver
public DatabaseConnection(){
try{
Class.forName("org.postgresql.Driver");
establishConnection();
} catch (ClassNotFoundException ce) {
System.out.println("Could not load jdbc Driver: ");
ce.printStackTrace();
}
}
public Connection establishConnection() {
// TODO Auto-generated method stub
try{
connection = DriverManager.getConnection(url, uname, pword);
} catch (SQLException e){
System.out.println("Could not connect to database: ");
e.printStackTrace();
}
return connection;
}
}
public class SingletonConnection {
private static DatabaseConnection con;
public SingletonConnection(){}
public static DatabaseConnection instance(){
assert con == null;
con = new DatabaseConnection();
return con;
}
}
This is my user table created by Pgadmin3 (hence the ugly upper cases):
CREATE TABLE "user"
(
id serial NOT NULL,
"userRoleId" integer NOT NULL,
"employeeId" bigint NOT NULL,
"subjectId" bigint NOT NULL,
username text NOT NULL,
cryptpwd text NOT NULL,
"userStatusId" integer NOT NULL,
md5pwd text NOT NULL,
CONSTRAINT pk_user PRIMARY KEY (id),
CONSTRAINT "subjectId" FOREIGN KEY ("subjectId")
REFERENCES subject (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT user_employee_id FOREIGN KEY ("employeeId")
REFERENCES employee (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "user_userRole_id" FOREIGN KEY ("userRoleId")
REFERENCES "userRole" (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "user_userStatus_id" FOREIGN KEY ("userStatusId")
REFERENCES "userStatus" (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "unique_user_userName" UNIQUE (username)
)
Since this application will be run on many machines in a local network, I would like to have only a single connection instance per specific user. That is, if userA logs in from one machine, and userA logs in from another machine moments later, notifications should appear on both machines with the second log in having the option to continue with the connection - in which case, the existing connection is dropped/lost.
I imagine I'd have to add a new column (logged_on boolean) in my user table ... in which case the second log in is handled by finding the value of logged_on and acting appropriately. My question is, how then will I be able to close the first connection? How can I maintain a maximum of one connection - per user - at database level?
Ok, this is what I'm working on. Surprisingly, I was thinking of something along the lines you mentioned Zamezela ... I haven't got it working yet, but I think this should work.
My user table:
CREATE TABLE "user"
(
id serial NOT NULL,
"userRoleId" integer NOT NULL,
"employeeId" bigint NOT NULL,
"subjectId" bigint NOT NULL,
username text NOT NULL,
cryptpwd text NOT NULL,
"userStatusId" integer NOT NULL,
md5pwd text NOT NULL,
"loggedIn" boolean NOT NULL DEFAULT false,
CONSTRAINT pk_user PRIMARY KEY (id),
CONSTRAINT "subjectId" FOREIGN KEY ("subjectId")
REFERENCES subject (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT user_employee_id FOREIGN KEY ("employeeId")
REFERENCES employee (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "user_userRole_id" FOREIGN KEY ("userRoleId")
REFERENCES "userRole" (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "user_userStatus_id" FOREIGN KEY ("userStatusId")
REFERENCES "userStatus" (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE,
CONSTRAINT "unique_user_userName" UNIQUE (username)
)
I've created a table that records each and every user login. Will help track down on user activity:
CREATE TABLE "userLoginHistory"
(
"userId" integer NOT NULL,
_datetime timestamp without time zone NOT NULL,
hostname text NOT NULL,
"osUsername" text NOT NULL,
id bigserial NOT NULL,
CONSTRAINT "pk_userLoginHistory" PRIMARY KEY (id),
CONSTRAINT "userLoginHistory_user_id" FOREIGN KEY ("userId")
REFERENCES "user" (id) MATCH FULL
ON UPDATE RESTRICT ON DELETE RESTRICT DEFERRABLE INITIALLY IMMEDIATE
)
I now have three main Stored functions thus far ... may add on to them tomorrow. Getting late.
First one involves requesting for a user login. This returns the user id, role, whether someone is logged on on this user account, and whether this user is active:
create type userLoginRequestReturnType as
(
userId integer, -- user.id
userRoleId integer, -- user.roleId
loggedIn boolean, -- user.loggedIn
userActive boolean -- whether user is active
);
CREATE OR REPLACE FUNCTION "user_login_request"(usernameIn text, passwordIn text)
returns setof userLoginRequestReturnType as
$$
declare
user_Id integer;
user_RoleId integer;
user_StatusId integer;
user_loggedIn boolean;
user_Active boolean;
sql text;
begin
user_Active = false;
select into user_Id, user_RoleId, user_StatusId, user_loggedIn id, "userRoleId", "userStatusId", "loggedIn" from "user" where username = usernameIn and cryptpwd = crypt(passwordIn, cryptpwd);
if (user_id > 0) then -- record found
select into user_Active "user_is_active"(user_StatusId);
else
user_id = 0;
user_RoleId = 0;
user_loggedIn = false;
user_Active = false;
end if;
sql = 'select ' || user_Id || ', ' || user_RoleId || ', ' || user_loggedIn || ', ' || user_Active ||';';
return query execute sql;
end;
$$ language 'plpgsql';
This is passed to the front end. If user_loggedIn is true, and all the other attributes support a successful log in, then the front end will notify the user that there is an existing connection, and whether to continue (disconnecting the existing connection). If it is false, then it just continues (without any prompt) to this function:
CREATE OR REPLACE FUNCTION "user_login_complete"(userIdIN integer, hostnameIN text, osUsernameIN text)
returns bigint as
$$
declare
currentTime timestamp without time zone;
userLoginHistoryId bigint;
begin
-- update user.loggedIn
update "user" set "loggedIn" = true where id = userIdIN;
-- insert into userLoginHistory
currentTime = NOW()::timestamp without time zone;
insert into "userLoginHistory" ("userId", _datetime, hostname, "osUsername") values (userIdIN, currentTime, hostnameIN, osUsernameIN);
select into userLoginHistoryId currval('"userLoginHistory_id_seq"');
return userLoginHistoryId;
end;
$$ language 'plpgsql';
The userLoginHistoryId is stored on the front end, since I'm using an MVC architecture for my Java Swing project, my abstract Model Class will call the following function in its constructor. I have taken your advice and will close the connection in each method.
-- function to check if the current logged in session is the last one recorded in database
-- to be run before each connection to the database as per userId
-- new userLoginHistoryId must be inserted into table userLoginHistory, and the id PK value stored in the front end
--
-- returns: true, if current session is the last session recorded in table userLoginHistory for this user_autosuggest_by_ID
-- : false, if another login session has been recorded.
-- MUST BE EXECUTED BEFORE EACH AND EVERY DATABASE TRANSACTION!!!!!
CREATE OR REPLACE FUNCTION "user_login_session_check"(userIdIN integer, userLoginHistoryIdIN bigint)
returns boolean as
$$
declare
results boolean;
userLoginHistoryId bigint;
begin
results = true;
select into userLoginHistoryId id from "userLoginHistory" where "userId" = userIdIN ORDER BY id DESC LIMIT 1;
if (userLoginHistoryIdIN = userLoginHistoryId) then
results = true;
else
results = false;
end if;
end;
$$ language 'plpgsql';
Will test tomorrow and hopefully it works fine. Please feel free to comment.
Thanks.
#greatkalu your problem is much deeper and very hard achievable, I will suggest you some approach: when user log in you should update two fields(last_access_timestamp, computer_id) and for every access to the database you should update last_access_timestamp. computer_id and last_access_time should be valid maybe 1 hour or less depends of the use of the application. when other person tries to login with same user_id then if now() - 1 hour < last_access_timestamp then that user should not be grant access.
computer_id is generated from application and for every computer should be unique and always generated same computer_id.
I hope this will help

Categories