During looking into the performance in my app, I noticed that for each object creation via JPA, framework calls T4CConnection.prepareStatement method in the Oracle JDBC driver (ojdbc8 19.3.0.0), code is constantly getting the table description via T4CConnection.doDescribeTable(), even if all inserts are for the same table. The retrieval of the table description takes around 20% longer then real insert statement execution. Can it be somehow tuned? Do you know what is the purpose to call table description every time for each insert, shouldn't it be cached for systems that schema does not change frequently?
Unfortunately it is hard to extract the code sample as it at framework level.
Stack is JPA, Hikari, OJDBC.
In general JPA saveAll method calls following:
com.zaxxer.hikari.pool.HikariProxyConnection.prepareStatement
com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate
PrepareStatement from Hikari internally calls a driver classes, where in my case it is: oracle.jdbc.driver.PhysicalConnection.prepareStatement()
Couldn't find the exact source code, but main concept is same as here https://github.com/mikesmithjr/ora-jdbc-source/blob/master/OracleJDBC/src/oracle/jdbc/driver/PhysicalConnection.java
Simplified code:
public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
(...) //check if insert statment
AutoKeyInfo info = new AutoKeyInfo(sql, columnNames);
doDescribeTable(info); <-- table describe occurs here, pretty slow
OraclePreparedStatement pstmt = (OraclePreparedStatement) prepareStatement(newSql); <-- in prepareStatement i can benefot from the cache
return pstmt;
}
Related
I work on some really old code (10 years+). And its (probably) practically impossible to get The Query that is going to be Executed later.
But I need to log the Queries in case of an error.
I tried to read the Metadata of the statement. But that does not do the job when I try to get the exact query.
System.out.println(preparedStatement); is also not working because we use a really old JDBC Driver, which does not implement a nice "toString" for the prepared Statement and I am not allowed to change the driver.
public void doQuery(Connection conn) throws SQLException{
PreparedStatement st=null;
ResultSet result=null;
st=createStatement(conn);
result = st.executeQuery();
...
}
public abstract PreparedStatement createStatement(Connection conn) throws SQLException;
the "createStatement" is an abstract method with 46 (magical) implementations, which are all different.
That's what makes it really hard for me to figure out how the "Statement" is created at all.
I only want to have something like
String query = preparedStatement.getQueryString();
Much time ago I used p6spy: a proxy for the real JDBC driver that logs real queries before passing them to the real driver.
Another solution is datasource-proxy that, as the name implies, proxies a DataSource, not the low-level driver. I've never tried it.
I'm trying to move a large number of records from one MySQL instance two another inside RDS. They are on different VPCs and different AWS accounts, so I can't create a data pipeline that would do the copy for me.
I've written a quick java program that connects to both the import database and the export database and does the following:
query the import database for the highest in table.primary_key with SELECT MAX(primary_key) FROM table
get a result set from the export table with SELECT * FROM table WHERE(primary_key > max_from_import) LIMIT 1000000
create a PreparedStatement object from the import connection and set the queryString to INSERT INTO table (col1....coln) VALUES (?....n?)
iterate over the result set and set the prepared statement columns to the ones from the result cursor (with some minor manipulations to the data), call execute on the PreparedStatement object, clear its' parameters, then move to the next result.
With this method I'm able to see around 100000 records being imported an hour, but I know that from this question that a way to optimize inserts is not to create a new query each time, but to append more data with each insert. i.e.
INSERT INTO table (col1...coln) VALUES (val1...valn), (val1...valn)....(val1...valn);
Does the jdbc driver know to do this, or is there some sort of optimization I can make on my end to improve insert run time?
UPDATE:
Both answers recommended using the add and execute batch, as well as removing auto commit. Removing auto commit saw a slight improvement (10%), doing the batch yielded a run time of less than 50% of the individual inserts.
You need to use batch insert. Internally, Connector/J (MySQL JDBC driver) can rewrite batch inserts into multi values insert statements.
(Note that this is the default Connector/J behavior. You can add
the option useServerPrepStmts=true to the JDBC url to enable server side prepared statements)
The code looks like the following:
try(PreparedStatement stmt = connection.prepareStatement(sql)) {
for(value : valueList) {
stmt.clearParameters();
stmt.setParameter(1, value);
stmt.addBatch();
}
stmt.executeBatch();
}
The code above will generate a multi value insert:
INSERT tablename(field) VALUES(value1), (value2), (value3) ...
First create a JDBC connection to Destination database and make its auto commit property to false.
After that in a loop do the following
Read N(for example 1000) number of rows from Source database and write that to destination database.
After some inserts commit destination database connection.
Sample code to get more idea is given below
Connection sourceCon = getSourceDbConnction();
Connection destCon = getDestinationDbConnction();
destCon.setAutoCommit(false);
int i=0;
String query;
while((query=getInsertQuery()!=null)
{
statement.executeUpdate(query);
i++;
if(i%10 == 0)
{
destCon.commit();
i=0;
}
}
destCon.commit();
The getInsertQuery function should give string in INSERT INTO table (col1...coln) VALUES (val1...valn), (val1...valn)....(val1...valn); format.
Also it should return null, if all tables are processed.
If you are using Prepared Statements, you can use addBatch and executeBatch functions. Inside loop add values using addBatch function. After some inserts call executeBatch.
I have some code I'm trying to fix and a function is used within a CallableStatement but it seems that said function is called in a particular instance where a Schema of the Database becomes the entire 'see-able' database for the function.
Let me explain :
My database (in PostgreSQL) has multiple projects (schemas) such as i2b2test_project or i2b2test_project2. Each of these projects has an architecture of multiple tables, such as visit_dimension, patient_dimension, etc..
Now, if I want to reference the visit_dimension table from a i2b2test_project, I need to use i2b2test_project.visit_dimension, as any logical SQL syntax would suggest.
The problem is that, in the code (which I'll include below), a CallableStatement is used to execute a function (plain old load-table-from-temp-table function). This function references the architecture mentioned above as if only one project exists, as the database. In other words, instead of referencing i2b2test_project.visit_dimension, it only references visit_dimension.
I haven't found a way to execute a function in some sort of 'separated instance' of the database and as a result, the function can not be used in a plain SQL statement inside a DB terminal, errors such as visit_dimension table does not exist etc..
So I ask : Does the call statement (which indeed seems to reference the schema) allow for such a 'separated instance' of the database ? And is there a way to do so with a plain SQL statement ?
Code :
CallableStatement callStmt = conn.prepareCall("{call "
+ this.getDbSchemaName()
+ "INSERT_ENCOUNTERVISIT_FROMTEMP(?,?,?)}");
callStmt.setString(1, tempTableName);
callStmt.setInt(2, uploadId);
callStmt.registerOutParameter(3, java.sql.Types.VARCHAR);
callStmt.execute();
I am running a Neo4j server (2.0.3) and am accessing it via Cypher queries through the Neo4j JDBC. It works well for most of the uses I have had so far.
I currently want to run a query that will return a result set that is of the type - TYPE_SCROLL_SENSITIVE and not the default TYPE_FORWARD_ONLY.
Here is the code I use to create my Statement and return the ResultSet:
Statement all = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);
results = all.executeQuery(query);
However when I try and use the method beforeFirst() on the ResultSet I get an SQLException. Which is a result of either a closed ResultSet or one that is TYPE_FORWARD_ONLY. I have used the isClosed() method to ensure it is not closed.
Within the Neo4j JDBC Driver there is the following createStatement method:
#Override
public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException
{
return debug(new Neo4jStatement(this));
}
So from what I can see, it is simply ignoring the type and concurrency constants that are passed to it. I realize not all JDBC drivers allow for different type result sets. I guess I am asking why the Neo4j one does not? And if not, are there any plans to implement support for this? I would find it useful, and thought others might as well?
I found out that Java doesn't let ResultSet to be return as a data type. And would like to ask whether is there any better solution to return results after executing query?
try {
Class.forName("com.mysql.jdbc.Driver");
Connection con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/shopping mall?zeroDateTimeBehavior=convertToNull", "root", "" );
java.sql.Statement stmt = con.createStatement() ;
String query = "SELECT * FROM shops WHERE SHOPNAME LIKE \'%" + shopName + "%\' ORDER BY shopname ASC"; // Select and sort using user input
ResultSet rs = stmt.executeQuery(query) ;
// How to return the result after executing the query?
}
catch ( SQLException err ) {
return null;
}
Don't mind the return null; part. Was testing something. Basically this is my current code for now. How can I return the result after executing query?
You need to provide better context for the error. Is this, for example, a JAX-WS web service endpoint? Anyway, as stated in the trace, your error is a web service error--not a JDBC error. This error can happen for many reasons--usually related to something wrong with the way you are defining the API to your service.
You are certainly allowed to return a ResultSet from a method even if that is a very bad idea, especially from a web service endpoint. A ResultSet can't be serialized into a SOAP message. More generally, to return a ResultSet betrays implementation details to the callers of the method. Why should they know you are using JDBC? Or even that you are talking to a relational (or any) database at all?
The better approach is to populate a model object relevant to your domain with the data in the ResultSet, and that object will be serialized to SOAP via JAXB or whatever you use. Or maybe you just return some text from the database, in which case JAX-WS knows what to do.
Also, make sure you do something with SQLException so you can trace the cause of your actual JDBC errors.
You write a method to retrieve info from a database, where should the data be processed and put into a model class? If you want code that is loosely coupled, then 98% of the time you would process the result set within the same method.
What happens if the query needs to change? You want the changes to be localized into a small of a subset of code as possible.
Take a look at Spring-JDBC JdbcTemplate. This method
List<Map<String, Object>> queryForList(String sql)
returns a List that contains a Map per row. You can use it or make something similar