Java insert data into database with Foreign Key - java

So I am creating a small application for a football organisation that needs to be able to add teams to the database.
My database has the following ERD:
I have the following code to add teams to my database:
public void toevoegenPloeg(Ploeg ploeg) throws DBException, ApplicationException {
//connectie tot stand brengen
System.out.println(ploeg.getTrainerID());
try (Connection connection = ConnectionManager.getConnection();) {
//statement opstellen
try (PreparedStatement statement = connection.prepareStatement("insert into ploeg (naam, niveau, trainer_id) values(?,?,?)");) {
statement.setString(1, ploeg.getNaam());
statement.setString(2, ploeg.getNiveau());
statement.setInt(3, ploeg.getTrainerID());
statement.execute();
} catch (SQLException ex) {
throw new DBException("SQL-exception in de toevoegenPloeg-methode - statement" + ex);
}
} catch (SQLException ex) {
throw new DBException("SQL-exception in de toevoegenPloeg-methode - connectie " + ex);
}
}
It has to be possible to add teams without a trainer.
Like this:
PloegTrans PT = new PloegTrans();
PersoonTrans PeT = new PersoonTrans();
Ploeg ploeg1 = new Ploeg();
ploeg1.setNiveau("U9");
PT.ploegToevoegen(ploeg1);
Trainer_id is an int and because I haven't defined the trainer_id.
The trainer_id becomes the default of an int, 0.
But then I get a Foreign Key exception, because the database looks for a trainer with id 0.
How can I overcome this?
How can I initialize my int as a "null"?

Use statement.setNull(3, java.sql.Types.INTEGER); or construct the statement as insert into ploeg (naam, niveau) values(?,?), so trainer_id will default to NULL.

On the POJO's side, the trainer ID should be a java.lang.Integer, not a primitive int, in order to allow nulls. On the JDBC side, you could use setObject instead of setInt, which does accept nulls:
// getTrainerID() returns either an Integer instance or null
statement.setObject(3, ploeg.getTrainerID());

Related

Return integer for the last row in MYSQL using JDBC

I'm new to working with JDBC commands. I have a database in MYSQL and each entry gets an ID. As initially created the ID was just a static variable that I iterated when the constructor runs. This was okay until I started deleting entries or running the program a second time. Then I start getting collisions. I need a way to return the highest row in the table and assign it to an integer that I can iterate.
The QuerySELECT MAX(ID) FROM table seems to get the value that I'm looking for. But I'm not sure of the syntax to get that value into an integer so I can return it.
public int getHighestRow() {
PreparedStatement ps;
int highestID = 0;
try {
ps = getSQLDB().prepareStatement("SELECT MAX(studentID) FROM student");
ps.execute();
} catch (SQLException e){
Logger.getLogger(Undergraduate.class.getName()).log(Level.SEVERE, null, e);
}
if (highestID > 0) return highestID;
else return 0;
I have a feeling this is very simple, but I wasn't able to find an existing answer. Or is there a more elegant way to do this in general?
SQL of different providers solve the retrieval of automatic generated keys differently. JDBC provides a standard solution.
Better use this JDBC solution, as it prevents mixing up those keys when insertions are done at the same time.
try (PreparedStatement ps = getSQLDB().prepareStatement(
"INSERT INTO student(....) VALUES(?, ..., ?)",
Statement.RETURN_GENERATED_KEYS)) { // Without StudentId
ps.setString(1, name);
...
ps.executeUpdate();
try (ResultSet rsKeys = ps.getGeneratedKeys()) {
if (rsKeys.next()) { // Only one record inserted
int studentId = rsKeys.getInt(1); // One key generated
}
}
} catch (SQLException e){
Logger.getLogger(Undergraduate.class.getName()).log(Level.SEVERE, null, e);
}
The mechanism with try(...). try-with-resources, ensures that close is called automatically.

h2 getGeneratedKeys throws exception

I try to put some Data in my H2 database but I'm a total noob in databases so it throws error over error since more than a hour.
Normaly I can fix it somehow but now I got a new problem I try to use
getGeneratedKeys() first I tried to use AUTO_INCREMENT(1,1) but that didn't works too function but it won't work rigth.
The exception my programm throws is
org.h2.jdbc.JdbcSQLException: Funktion "GETGENERATEDKEYS" nicht gefunden
Function "GETGENERATEDKEYS" not found; SQL statement:
insert into logTbl values( getGeneratedKeys(),Webservice->startThread0: Thread0) [90022-173]
an my database function looks like this
public void createTable(String Log) {
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
System.err.println("TREIBER FEHLER");
e.printStackTrace();
}
Connection conn = null;
try {
conn = DriverManager.getConnection("jdbc:h2:~/DBtest/Logs");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE IF NOT EXISTS logTbl(ID INT PRIMARY KEY, LOG VARCHAR(255))");
//stat.execute("insert into test values(1, 'Hello')");
for (int i = 0; i < 20; i++) {
stat.execute("insert into logTbl values( getGeneratedKeys()," + Log + ")");
}
stat.close();
conn.close();
} catch (SQLException e) {
System.err.println("SQL FEHLER");
e.printStackTrace();
}
}
hope you can help me to fix my error as I said I'm totaly new and just had some code example as "tutorial" because I don't found a good tutorial
If you want to automatically generate primary key values, you need to first change the definition of your table:
CREATE TABLE IF NOT EXISTS logTbl
(
ID integer AUTO_INCREMENT PRIMARY KEY,
LOG VARCHAR(255)
);
You should also use a PreparedStatement rather than concatenating values.
So your Java code would look something like this:
String insert = "insert into logTbl (log) values(?)";
PreparedStatement pstmt = connection.prepareStatement(insert, Statement.RETURN_GENERATED_KEYS);
pstmt.executeUpdate();
ResultSet rs = pstmt.getGeneratedKeys();
long id = -1;
while (rs.next())
{
rs.getLong(1);
}
It might be that you need to use the overloaded version of prepareStatement() where you supply the column to be returned. Not sure which one works with H2:
prepareStatement(insert, new String[] {"ID"});
Btw: there is nothing "magic" about 255 as the length of a varchar column. There is no performance difference between varchar(500), varchar(20)or varchar(255). You should use the length that you expect you need, not some "magic" limit you think performs better.

JPA number generators without primary key and general use

Environment: OpenJPA2.x, Tomcat webapp, Mariadb but may be changed. This is not Hibernate or Spring webapp.
I have read few topics already such as this:
Hibernate JPA Sequence (non-Id)
I have few entity classes with someNumber non-primary key field, some have someNumber and someNumberB twin columns. Fields have a constraint UNIQUE(someNumber) and UNIQUE(someNumberB), primary key composite is PRIMARY(server_id, code). I need a numeric value before commiting row inserts.
If I understood other topics I cannot use JPA #generator tags. I am forced to implement an old-school utility method. This is a method I did, it takes a fresh db connection so it's always run in a separate transaction.
public synchronized static long getGeneratorValue(String genName, int incStep) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
if (genName==null) genName="app";
// try few times before give up, another node may have updated a value in-between. Create a new transaction from db connection pool.
for(int idx=0; idx<3; idx++) {
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
rs = stmt.executeQuery(String.format("Select value From generators Where name='%s'", genName));
if (!rs.next()) throw new IllegalArgumentException("Invalid generator name " + genName);
if (incStep==0)
return rs.getLong("value"); // return an existing value
long oldValue = rs.getLong("value");
long newValue = oldValue+incStep;
int rowCount = stmt.executeUpdate(String.format("Update generators Set value=%d Where name='%s' and value=%d", newValue, genName, oldValue));
if (rowCount>0) {
conn.commit();
return newValue;
}
close(rs, stmt, conn);
conn=null;
}
throw new IllegalArgumentException("Obtaining a generator value failed " + genName);
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}
I am not fully happy with this, especially a failsafe foreach_loop against another Tomcat node updated generator value in-between concurrently. This loop may fail on busy workloads.
Could I use DB auto_increment column as a general purpose number generator, I suppose it tolerates better concurrency? If this locks a database to MariaDB,MySQL or similar I can live with that for now.
Value must be a numeric field for legacy purpose, I cannot use GUID string values.
I was thinking about using DB auto_increment column and came up with this utility function. I probably go with this implementation or do StackOverflow community have better tricks available?
CREATE TABLE generatorsB (
value bigint UNSIGNED NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY(value)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_swedish_ci;
// name=any comment such as an entityClass name, no real application use
public static long getGeneratorBValue(String name) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
String sql = String.format("Insert Into generatorsB (name) Values('%s')", name);
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
int rowCount = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
if (rowCount>0) {
rs = stmt.getGeneratedKeys();
if (rs.next()) {
long newValue = rs.getLong(1);
if (newValue % 5 == 0) {
// delete old rows every 5th call, we don't need generator table rows
sql = "Delete From generatorsB Where value < "+ (newValue-5);
stmt.close();
stmt = conn.createStatement();
stmt.executeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
conn.commit();
return newValue;
}
}
throw new IllegalArgumentException("Obtaining a generator value failed");
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}

ResultSet is Closed

Following is my Table Definition:
create Table alarms(
alarmId int primary key identity(1,1),
alarmDate varchar(50) not null,
alarmText varchar(50) not null,
alarmStatus varchar(10) Check (alarmStatus in(-1, 0, 1)) Default 0
);
Secondly here are some of my methods i'm using:
public void restartDatabase(){
try{
Class.forName(Settings.getDatabaseDriver());
connection = DriverManager.getConnection( Settings.getJdbcUrl() );
statement = connection.createStatement();
}
catch(Exception e){
e.printStackTrace();
}
}
public ResultSet executeQuery(String query){
ResultSet result = null;
try {
result = statement.executeQuery(query);
} catch (SQLException e) {
e.printStackTrace();
}
return result;
}
public void closeDatabase() {
try {
if ((statement != null) && (connection != null)) {
statement.close();
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
What i want to do is to get all the alarmId's from the table where date is equal to the given date and then against each alarmId i want to update its status to given status:
public static void updateAlarmStatus(int status) {
ResultSet rs = null;
database.restartDatabase();
try {
rs = database
.executeQuery("Select alarmId from alarms where alarmDate = '"
+ Alarm.getFormattedDateTime(DateFormat.FULL,
DateFormat.SHORT) + "'");
while (rs.next()) {
database.executeUpdate("update alarms set alarmStatus = '"+status+"' where alarmId = '"+rs.getString("alarmId")+"'");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
database.closeDatabase();
}
}
But it generates the Error that Result Set is Closed.
I Goggled it and came to know that a result set automatically closes when we try to execute another query inside it
and it needs to restart the connection.
i tried calling restartDatabase() method that is creating new connection but still getting the same error.
I'm guessing executeUpdate uses the same instance variable for its Statement as the query uses. When you create a new Statement and assign it to the variable, nothing is referring to the old one, so it gets cut loose and becomes subject to garbage-collection. During garbage collection the statement's finalizer is invoked, closing it. Closing the statement makes the ResultSet it created close as well.
You shouldn't be sharing these Statement variables between different queries and updates. The statement should be a local variable and not a member of an object instance.
Also, result Sets should always be local variables, they shouldn't be passed outside the method where they're created. The resultSet is a reference to a cursor, it doesn't actually hold any data. Always have your code read from the resultSet and populate some data structure with the results, then return the data structure.
You can also select and change all alarmIds at once:
rs = database.
executeQuery("Select group_concat(distinct alarmId) as alarmIds from alarms group by alarmDate having alarmDate = '"
+ Alarm.getFormattedDateTime(DateFormat.FULL,
DateFormat.SHORT) + "'");
while (rs.next()) { // there will be only one result
database.executeUpdate("update alarms set alarmStatus = '"+status+"' where alarmId in ("+rs.getString("alarmIds")+")");
}

Inserting preparedstatement to database - PSQL

This seems like a really simple problem, but I cannot figure out what my problem is. I have a method addTask which adds some info to our database as seen in this code:
public static boolean addTask(String name, String question, int accuracy, int type){
StringBuilder sql = new StringBuilder();
sql.append("INSERT INTO tasks (name, question, type, accuracy) ");
sql.append("VALUES(?, ?, ?, ?)");
try {
Connection c = DbAdaptor.connect();
PreparedStatement preparedStatement = c.prepareStatement(sql.toString());
preparedStatement.setString(1, name);
preparedStatement.setString(2, question);
preparedStatement.setInt(3, type);
preparedStatement.setInt(4, accuracy);
preparedStatement.execute();
preparedStatement.close();
c.close();
return true;
}
catch (SQLException e) {
e.printStackTrace();
return false;
}
}
my problem is that preparedStatement.execute() always returns false, indicating the information hasnt been added to the database. I can run psql and this confirms that nothing has been written to the db. The connection definitely connects to the correct database (i put in some other printlns etc. to check this). I am trying to insert into a newly initialised table that looks like this:
CREATE TABLE tasks
(
id SERIAL PRIMARY KEY,
submitter INTEGER REFERENCES accounts (id),
name VARCHAR(100) NOT NULL,
question VARCHAR(100) NOT NULL,
accuracy INTEGER NOT NULL,
type INTEGER REFERENCES types (id),
ex_time TIMESTAMP,
date_created TIMESTAMP
);
code for DbAdaptor.connect():
public static Connection connect(){
try {
Class.forName("org.postgresql.Driver");
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Properties properties = new Properties();
properties.setProperty("user", USER);
properties.setProperty("password", PASSWORD);
try {
return DriverManager.getConnection(URL, properties);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
where USER and PASSWORD are static fields in the class
You misunderstood the return value of PreparedStatement#execute().
Please carefully read the javadoc:
Returns:
true if the first result is a ResultSet object; false if the first result is an update count or there is no result.
It thus returns — as fully expected — false on an INSERT query. It returns only true on a SELECT query (for which you'd however usually like to use executeQuery() instead which returns directly a ResultSet).
If you're interested in the affected rows, rather use PreparedStatement#executeUpdate() instead. It returns an int as per the javadoc:
Returns:
either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements that return nothing
A return value of 1 or greater would then indicate a successful insert.
Unrelated to the concrete problem: your code is leaking DB resources. Please carefully read How often should Connection, Statement and ResultSet be closed in JDBC?

Categories

Resources