JPA: How to INSERT setting PK to MAX(PK) + 1 - java

Scenario: I came across some code that is mixing JPA with JDBC within a transaction. The JDBC is doing an INSERT into a table with basically a blank row, setting the Primary Key to (SELECT MAX(PK) + 1) and the middleName to a temp timestamp. The method is then selecting from that same table for max(PK) + that temp timestamp to check if there was a collision. If successful, it then nulls out the middleName and updates. The method returns the newly created Primary Key.
Question:
Is there a better way to insert an entity into the database, setting the PK to max(pk) + 1 and gaining access to that newly created PK (preferably using JPA)?
Environment:
Using EclipseLink and need to support several versions of both Oracle and MS SqlServer databases.
Bonus Background: The reason I'm asking this question is because I run into a java.sql.BatchUpdateException when calling this method as part of a chain when running integration tests. The upper part of the chain uses JPA EntityManager to persist some objects.
Method in question
#Override
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public int generateStudentIdKey() {
final long now = System.currentTimeMillis();
int id = 0;
try {
try (final Connection connection = dataSource.getConnection()) {
if (connection.getAutoCommit()) {
connection.setAutoCommit(false);
}
try (final Statement statement = connection.createStatement()) {
// insert a row into the generator table
statement.executeUpdate(
"insert into student_demo (student_id, middle_name) " +
"select (max(student_id) + 1) as student_id, '" + now +
"' as middle_name from student_demo");
try (final ResultSet rs = statement.executeQuery(
"select max(student_id) as student_id " +
"from student_demo where middle_name = '" + now + "'")) {
if (rs.next()) {
id = rs.getInt(1);
}
}
if (id == 0) {
connection.rollback();
throw new RuntimeException("Key was not generated");
}
statement.execute("update student_demo set middle_name = null " +
"where student_id = " + id);
} catch (SQLException statementException) {
connection.rollback();
throw statementException;
}
}
} catch (SQLException exception) {
throw new RuntimeException(
"Exception thrown while trying to generate new student_ID", exception);
}
return id;
}

First off: it hurts to answer this. But I know, sometimes you have to deal with the devil :(
So technically, it's not JPA, but if you are using Hibernate as JPA-Provider, you can go with
#org.hibernate.annotations.GenericGenerator(
name = “incrementGenerator”,
strategy = “org.hibernate.id.IncrementGenerator”)
#GeneratedValue(generator="incrementGenerator")
private Long primaryKey;
The Hibernate solution is "thread-safe", but not "cluster-safe", i.e. if you run your application on several hosts, this may fail. You may catch the appropriate exception and try again.
If you stick with your solution: close the ResultSet, Statement and the Connection. Sorry, didn't catch the try-with-resources initially.

The JDBC code is pathological, makes no sense, and will not work in a multi user environment.
I would strongly recommend fixing the code to use a sequence object, or sequence table.
In JPA you can just use sequencing.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Sequencing
If you really want to do your own sequencing, you can either assign the Id yourself, use PrePersist to assign your own id, or in EclipseLink implement your own Sequence subclass that does whatever you desire. You will need to register this Sequence object using a SessionCustomizer.
See,
http://wiki.eclipse.org/EclipseLink/Examples/JPA/CustomSequencing

Related

I cannot see the update record after flush

Passing from glassfish3 to payara5 one piece of code stop waorking. Our code save a date to a set of record and the select those updated record. In glassfish3 work seamlessly, in payara5 the select return no record (seems like it does work in another transaction). If the result is different we throw an exception, so the data is never saved.
The transaction scope is READ_COMMITTED
try {
String whereClause = " where "
+ "em.mailboxToBeUsed =:mailbox "
+ "and em.lastSendResult is null "
+ "and (em.errorsNumber is null or em.errorsNumber<4) ";
Query updateQuery = em.createQuery("update EmailTobeSended em "
+ "set em.trysendsince=:dat " + whereClause);
updateQuery.setParameter("mailbox", mb);
updateQuery.setParameter("dat", key);
int modificate = updateQuery.executeUpdate();
em.flush();
TypedQuery<EmailTobeSended> emlocksel = em.createQuery(
"select em from EmailTobeSended em WHERE em.mailboxToBeUsed =:mailbox AND "
+ "em.trysendsince=:dat "
+ " order by em.emailId ", EmailTobeSended.class);
emlocksel.setParameter("mailbox", mb);
emlocksel.setParameter("dat", key);
res = emlocksel.getResultList();
if (modificate != res.size()) {
throw new java.lang.AssertionError("Lock error on select emailtobesended");
}
} catch (Exception ex) {
gotError = true;
res = null;
}
On glassfish3, after flushing, the second query find out the record updated. On payara5 no result
EDIT
we use eclipselink
WE solved the problem: it wasn't about persistence, it was about mysql versions (from 5.5 to 5.6)
The DATE field was interpreted differently between the two version: in 5.5 millisecond are ignored, in 5.6 are considered. Due to the field was not configured to accept millisecond, the date were saved without them, so in the second query (a select) the comparison were done answering with ".000" as millisecond, different from what were searched
Updating the field to DATE(3) solved the problem

How to manage SQL database using associations and not using keys/ids?

I need some help.
We get an assignment and the teacher required that we use associations and not using keys/ids. I don't really understand the concept, and how I solve it using SQL database. What I had learned in the database course that we use keys/id to link the tables together. A brief deception of the problem, we have a yacht club, in the club, we can register, delete and edit member. For every member, we can register one or more boat. A member can edit and delete a boat. The problem Should be solved by Model-View separation.
I have solved the problem by creating two tables and link the boat table to the member by memberId. The teacher told me it is not correct to use ID(He said: Basically the UI should add Boats to a Member object, not insert boats in a database with a member id). Can you please give me suggestions on how to solve the issue without using Id.
public void createTables() {
Statement tablesSt;
try {
tablesSt = conn.createStatement();
tablesSt.executeUpdate( "CREATE TABLE IF NOT EXISTS "+ Boat +
" (Boat_ID INT PRIMARY KEY," +
" Member_ID INT NOT NULL,"+
" Size INT NOT NULL ,"+
" Type TEXT NOT NULL ,"+
" FOREIGN KEY (Member_ID) REFERENCES " + Member + "(Member_ID) ON DELETE CASCADE) ");
tablesSt.close();
tablesSt = conn.createStatement();
tablesSt.executeUpdate( "CREATE TABLE IF NOT EXISTS "+ Member +
" (Member_ID INT PRIMARY KEY," +
" Member_Name TEXT NOT NULL ," +
" Personal_Number INT UNIQUE )");
tablesSt.close();
A code from boat class
public void newBoat(int memberID, String type, int size ) {
int BoatId= 0;
try {
ResultSet rc= Control.conn.createStatement().executeQuery("SELECT * FROM Boat ORDER BY Boat_ID DESC LIMIT 1");
BoatId =rc.getInt("Boat_ID") +1;
Statement registerBoat = Control.conn.createStatement();
registerBoat.executeUpdate("INSERT OR IGNORE INTO Boat VALUES("+BoatId+" , "+memberID+" , '"+size+"', '"+type+"')");
Control.conn.commit();
Control.conn.setAutoCommit(false);
/* ResultSet rs2 = Control.conn.createStatement().executeQuery(" SELECT * FROM Boat WHERE Boat_ID ="+ BoatId);
System.out.println(rs2.getString("type"));*/ // test code to check if the Boat is Succesfully registered
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

execute SQL query that include Temp table that DROP and Create Temp and then SELECT [duplicate]

In MySQL I have two tables, tableA and tableB. I am trying to execute two queries:
executeQuery(query1)
executeQuery(query2)
But I get the following error:
can not issue data manipulation statements with executeQuery().
What does this mean?
To manipulate data you actually need executeUpdate() rather than executeQuery().
Here's an extract from the executeUpdate() javadoc which is already an answer at its own:
Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement or an SQL statement that returns nothing, such as an SQL DDL statement.
When executing DML statement , you should use executeUpdate/execute rather than executeQuery.
Here is a brief comparison :
If you're using spring boot, just add an #Modifying annotation.
#Modifying
#Query
(value = "UPDATE user SET middleName = 'Mudd' WHERE id = 1", nativeQuery = true)
void updateMiddleName();
For Delete query - Use #Modifying and #Transactional before the #Query like:-
#Repository
public interface CopyRepository extends JpaRepository<Copy, Integer> {
#Modifying
#Transactional
#Query(value = "DELETE FROM tbl_copy where trade_id = ?1 ; ", nativeQuery = true)
void deleteCopyByTradeId(Integer id);
}
It won't give the java.sql.SQLException: Can not issue data manipulation statements with executeQuery() error.
Edit:
Since this answer is getting many upvotes, I shall refer you to the documentation as well for more understanding.
#Transactional
By default, CRUD methods on repository instances are transactional. For read operations,
the transaction configuration readOnly flag is set to true.
All others are configured with a plain #Transactional so that default transaction
configuration applies.
#Modifying
Indicates a query method should be considered as modifying query as that changes the way
it needs to be executed. This annotation is only considered if used on query methods defined
through a Query annotation). It's not applied on custom implementation methods or queries
derived from the method name as they already have control over the underlying data access
APIs or specify if they are modifying by their name.
Queries that require a #Modifying annotation include INSERT, UPDATE, DELETE, and DDL
statements.
Use executeUpdate() to issue data manipulation statements. executeQuery() is only meant for SELECT queries (i.e. queries that return a result set).
#Modifying
#Transactional
#Query(value = "delete from cart_item where cart_cart_id=:cart", nativeQuery = true)
public void deleteByCart(#Param("cart") int cart);
Do not forget to add #Modifying and #Transnational before #query. it works for me.
To delete the record with some condition using native query with JPA the above mentioned annotations are important.
That's what executeUpdate is for.
Here's a very brief summary of the difference: http://www.coderanch.com/t/301594/JDBC/java/Difference-between-execute-executeQuery-executeUpdate
This code works for me: I set values whit an INSERT and get the LAST_INSERT_ID() of this value whit a SELECT; I use java NetBeans 8.1, MySql and java.JDBC.driver
try {
String Query = "INSERT INTO `stock`(`stock`, `min_stock`,
`id_stock`) VALUES ("
+ "\"" + p.get_Stock().getStock() + "\", "
+ "\"" + p.get_Stock().getStockMinimo() + "\","
+ "" + "null" + ")";
Statement st = miConexion.createStatement();
st.executeUpdate(Query);
java.sql.ResultSet rs;
rs = st.executeQuery("Select LAST_INSERT_ID() from stock limit 1");
rs.next(); //para posicionar el puntero en la primer fila
ultimo_id = rs.getInt("LAST_INSERT_ID()");
} catch (SqlException ex) { ex.printTrace;}
executeQuery() returns a ResultSet. I'm not as familiar with Java/MySQL, but to create indexes you probably want a executeUpdate().
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/java_swing_db", "root", "root");
Statement smt = conn.createStatement();
String sql = "SELECT * FROM `users` WHERE `email` = " + email + " AND `password` = " + password + " LIMIT 1;";
String registerSql = "INSERT INTO `users`(`email`, `password`, `name`) VALUES ('" + email + "','" + password + "','" + name + "')";
System.out.println("SQL: " + registerSql);
int result = smt.executeUpdate(registerSql);
System.out.println("Result: " + result);
if (result == 0) {
JOptionPane.showMessageDialog(this, "This is alredy exist");
} else {
JOptionPane.showMessageDialog(this, "Welcome, Your account is sucessfully created");
App.isLogin = true;
this.dispose();
new HomeFrame().show();
}
conn.close();
Besides executeUpdate() on the parentheses, you must also add a variable to use an SQL statement.
For example:
PreparedStatement pst = connection.prepareStatement(sql);
int numRowsChanged = pst.executeUpdate(sql);

Database Query working on DBMS directly but not in Java

I am trying to do things before processing Ingest to KIT DataManager (Code on GitHub, it runs on tomcat7) with a "Staging Processor" …
adding a custom Staging Processor
package edu.kit.dama.mdm.content.mets;
…
public class TryQuota extends AbstractStagingProcessor {
…
#Override
public final void performPreTransferProcessing(TransferTaskContainer pContainer) throws StagingProcessorException {
…
trying to get user data
… this works
UserData userResult = null;
try {
userResult = mdm.findSingleResult(
"Select u FROM UserData u WHERE u.email=?1",
new Object[]{"dama#kit.edu"},
standard email of admin user with userid 1
UserData.class
);
} catch (UnauthorizedAccessAttemptException e2) {
System.out.println("exception on extracting userid");
e2.printStackTrace();
}
try {
System.out.println("KIT DM ID: " + userResult.getUserId());
}catch(Exception e4) {
System.out.println("exception on output for userid");
e4.printStackTrace();
}
trying to get quota from UserQuota
and on the other hand, the corresponding implementation doesn't do the job here (that I want to get working)
Number UserQuota = null;
try {
UserQuota = mdm.findSingleResult(
//SQL would be: "SELECT quota FROM userquota WHERE uid=?;"
//JPQL is …
"Select q.quota FROM UserQuota q WHERE q.uid=?1",
new Object[]{1},
Number.class
);
} catch (UnauthorizedAccessAttemptException e2) {
System.out.println("exception on userquota");
e2.printStackTrace();
}
System.out.println("quota is: " + UserQuota );
UserQuota is still null here
DB is PostgreSQL, Table is:
CREATE SEQUENCE userquota_seq
START WITH 1
INCREMENT BY 1
NO MAXVALUE
NO MINVALUE;
CREATE TABLE userquota
(
id INTEGER NOT NULL DEFAULT nextval('userquota_seq'),
uid INTEGER NOT NULL DEFAULT 0,
quota DECIMAL NOT NULL DEFAULT 0,
CONSTRAINT uid_key UNIQUE (uid),
CONSTRAINT fk_uid FOREIGN KEY (uid) REFERENCES users(id)
);
This quota I want to get from db in the processor
INSERT INTO userquota (id, uid,quota) VALUES ( 0, 1, 1048576 );
So mainly I want to get the entry for the ingesting user (here 1) from db: 1048576 as a Long.
Any hints welcome on how to proceed on these things.
As stated in above comment, the following statement is invalid SQL syntax:
SELECT u FROM UserData u WHERE u.email=?1
Instead, according to above comment the correct syntax would be:
SELECT u.* FROM UserData u WHERE u.email='dama#kit.edu'
I found a solution by creating a class UserQuota1 so JPQL can work here.
As I did not find a way around, I first copied class UserData, dropped everything I had no use of and changed the members according to my database table userquota. This also means when extending the db table this class needs to be altered as well.
A very important part I had no clue about is that this new class also needs to be registered for tomcat7 in persistence.xml, also here copied over from the entry for UserData.

Creating a database table if it does not exist in Java production code and confirming in JUnit

I am writing a database program in Java and want to create a table if it does not already exist. I learned about DatabaseMetaData.getTables() from How can I detect a SQL table's existence in Java? and I am trying to use it:
private boolean tableExists() throws SQLException {
System.out.println("tableExists()");
DatabaseMetaData dbmd = conn.getMetaData();
ResultSet rs = dbmd.getTables(null, null, this.getTableName(), null);
System.out.println("TABLE_NAME: " + rs.getString("TABLE_NAME"));
return rs.getRow() == 1;
}
The problem is that rs.getRow() always returns 0, even after the table has been created. Using rs.getString("TABLE_NAME") throws an exception stating that the result set is empty.
One possible solution I thought of is to execute the CREATE TABLE statement and catch any exceptions that are thrown. However, I don't like the idea of using exceptions for control flow of my program.
FWIW, I am using HSQLDB. However, I would like write Java code that is independent of the RDMS engine. Is there another way to use DatabaseMetaData.getTables() to do what I want? Or is there some other solution to write my tableExists() method?
Added:
Using the suggestions given here, I found a solution that seems to work in my production code:
private void createTable() throws SQLException {
String sqlCreate = "CREATE TABLE IF NOT EXISTS " + this.getTableName()
+ " (brand VARCHAR(10),"
+ " year INTEGER,"
+ " number INTEGER,"
+ " value INTEGER,"
+ " card_count INTEGER,"
+ " player_name VARCHAR(50),"
+ " player_position VARCHAR(20))";
Statement stmt = conn.createStatement();
stmt.execute(sqlCreate);
}
Now I am also writing a JUnit test to assert that the table does indeed get created:
public void testConstructor() throws Exception {
try (BaseballCardJDBCIO bcdb = new BaseballCardJDBCIO(this.url)) {
String query = "SELECT count(*) FROM information_schema.system_tables WHERE table_name = '" + bcdb.getTableName() + "'";
Connection conn = DriverManager.getConnection(this.url);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
Assert.assertTrue(rs.next());
Assert.assertEquals(1, rs.getInt(1));
Assert.assertFalse(rs.next());
}
}
This test fails on the assertEquals() with the following message:
FAILED: expected: <1> but was: <0>
The solution I found seems to work:
private void createTable() throws SQLException {
String sqlCreate = "CREATE TABLE IF NOT EXISTS " + this.getTableName()
+ " (brand VARCHAR(10),"
+ " year INTEGER,"
+ " number INTEGER,"
+ " value INTEGER,"
+ " card_count INTEGER,"
+ " player_name VARCHAR(50),"
+ " player_position VARCHAR(20))";
Statement stmt = conn.createStatement();
stmt.execute(sqlCreate);
}
I had to place the IF NOT EXISTS in the correct location in my SQL statement.
From the ResultSet definition at Java docs:
A ResultSet object maintains a cursor pointing to its current row of
data. Initially the cursor is positioned before the first row. The
next method moves the cursor to the next row, and because it returns
false when there are no more rows in the ResultSet object, it can be
used in a while loop to iterate through the result set.
So, you must always call the next() method otherwise getRow() will always return zero as the cursor is positioned before the first row.
There is build in mysql functionality for what you seek: http://dev.mysql.com/doc/refman/5.0/en/create-table.html
In short: Just append IF NOT EXISTS at the end of your table creation query.
Edit:
There is no general way of doing this. Most databases have an information_scheme table though, a query to determine the information could look like this:
SELECT count(*)
FROM information_schema.system_tables
WHERE table_schem = 'public' AND table_name = 'user';
This works with sqlite, mysql, msql, mariadb and postgres + probably a lot of others.
I don't know if this will necessarily help towards your goals, but when I ran into this problem using Python and MySQL, I just added a "Drop Table" statement before each "Create Table" statement, such that just running the script automatically deletes the existing table, and then rebuilds each table. That may not work for your needs, but it's one solution that I found successful for my similar problem.

Categories