My question is similar to this one, but more specific.
I have multiple classes that execute queries against the same database. The problem is that the host may occasionally experience problems with the Internet connection, and my application must be designed against that knowledge.
So I cannot just create java.sql.Connection once, because it can break at any time. If a connection breaks while executing one of the queries - it's OK for me. The easiest way is to just create a new one in each query method:
public static Profile getProfileBySteamID(long userid) {
try (Connection connection = DriverManager.getConnection(Globals.dbConnection);
Statement st = connection.createStatement()) {
final ResultSet res = st.executeQuery("SELECT " +
"p_id, p_name, p_id_dis, p_uid, " +
"p_lastupd, p_lastservertime, p_roles " +
"FROM profiles WHERE p_uid='" + userid + "'");
if (!res.next())
throw new RuntimeException("No such profile");
long disID = res.getLong("p_id_dis");
String steamid = res.getString("p_uid");
return new Profile(steamid, disID);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static Profile getProfileByDisID(long userid) {
try (Connection connection = DriverManager.getConnection(Globals.dbConnection);
Statement st = connection.createStatement()) {
final ResultSet res = st.executeQuery("SELECT " +
"p_id, p_name, p_id_dis, p_uid, " +
"p_lastupd, p_lastservertime, p_roles " +
"FROM profiles WHERE p_id_dis='" + userid + "'");
if (!res.next())
throw new RuntimeException("No such profile");
long disID = res.getLong("p_id_dis");
String steamid = res.getString("p_uid");
return new Profile(steamid, disID);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
// ... and about 7 more methods like these
I'm OK with the possible performance issues (because these queries are executed not so frequently), but this is too much repeated code that is hard to extract to other private methods, and, you know, kinda looks ugly.
So my question is how to get that java.sql.Statement (or better - PreparedStatement) in as short expression as possible, and without the try-catch statements if possible. Maybe I should consider using ConnectionPool?
I'd like to get some examples of the solution, or links to source code examples of ConnectionPool with PreparedStatements usage or anything similar
Write a helper method which does the common code:
public class DBHelper {
public interface SqlFunction<T> {
T apply(Statement statement) throws SQLException;
}
public static <T> T withStatement(SqlFunction<T> task) {
try (Connection connection = DriverManager.getConnection(Globals.dbConnection);
Statement st = connection.createStatement()) {
return task.apply(st);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
Profile p = DBHelper.withStatement(st -> {
final ResultSet res = st.executeQuery("SELECT " +
"p_id, p_name, p_id_dis, p_uid, " +
"p_lastupd, p_lastservertime, p_roles " +
"FROM profiles WHERE p_id_dis='" + userid + "'");
if (!res.next())
throw new RuntimeException("No such profile");
long disID = res.getLong("p_id_dis");
String steamid = res.getString("p_uid");
return new Profile(steamid, disID);
});
}
}
By the way, you should be using PreparedStatement -- it handles escaping parameters, so it is both easier to use and safer against malicious SQL injection.
Using a connection pool, e.g. Hikari, is also a good idea in addition to the approach I suggest above. It will handle broken connections and reduce the overhead of making new connections. It will also allow you to limit the number of concurrent connections your application will use.
Related
I created a method to get the values from a database in java using SQL and store the information in a ResultSet and then use a while loop to store the information in a RentSendItem and store all those items in an ArrayList called sendList but when I try to run it, it gives me the error:
'ResultSet not open. Operation 'getString' not permitted. Verify that autocommit is off'
This is my class:
public void getDataFromDB() {
System.out.println("Wordk");
//connecting
Connection connection = null;
Statement statement = null;
try {
System.out.println("1");
connection = DriverManager.getConnection(url, username, password);
statement = connection.createStatement();
ResultSet name = statement.executeQuery("SELECT firstname,surname FROM CUSTOMER");
ResultSet titles = statement.executeQuery("Select Title,Category From ADDDVD ");
System.out.println(name.getString("firstname"));
System.out.println("2");
while (name.next()) {
String fullName = name.getString("firstname") + " " + name.getString("surname");
RentSendItem item = new RentSendItem(name.getString("firstname") + name.getString("surname"), titles.getString("Category"), titles.getString("title"));
sendList.add(item);
}
System.out.println("3");
} catch (Exception e) {
System.out.println("Error" + e.getMessage());
}
}
So just want to know what am I doing wrong and will this class do what I want it to do. If you could maybe help me, I would be grateful.
There are several problems in your code.
You can't call method getString() of interface java.sql.ResultSet before you call method next(). First call method next() and if that method returns "true", then you can call method getString().
You also need to call method next() on titles.
You can't call getString() twice on the same column on the same row.
Compare the below with your code.
public void getDataFromDB() {
System.out.println("Wordk");
// connecting
try (Connection connection = DriverManager.getConnection(url, username, password);
Statement statement = connection.createStatement()) {
System.out.println("1");
ResultSet name = statement.executeQuery("SELECT firstname,surname FROM CUSTOMER");
ResultSet titles = statement.executeQuery("Select Title,Category From ADDDVD ");
System.out.println("2");
while (name.next()) {
String firstname = name.getString("firstname");
String surname = name.getString("surname");
String fullName = firstname + " " + surname;
if (titles.next()) {
RentSendItem item = new RentSendItem(fullName,
titles.getString("Category"),
titles.getString("title"));
sendList.add(item);
}
}
System.out.println("3");
}
catch (Exception e) {
e.printStackTrace();
}
}
Also, the following are not problems but recommendations.
In the catch block, it usually preferable to print the entire stack trace rather than just the error message.
You should close the Connection and Statement once you no longer need them. In the code above, I have used try-with-resources
One simple optimization for SQL is the reuse of prepared statements. You incur the parsing cost once and can then reuse the PreparedStatement object within a loop, just changing the parameters as needed. This is clearly documented in Oracle's JDBC tutorial and many other places.
Spring 5 when using JdbcTemplate seems to make this impossible. All JdbcTemplate query and update methods that deal with PreparedStatementCreators funnel down to one execute method. Here's the code of that method in its entirety.
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)
throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
if (logger.isDebugEnabled()) {
String sql = getSql(psc);
logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));
}
Connection con = DataSourceUtils.getConnection(obtainDataSource());
PreparedStatement ps = null;
try {
ps = psc.createPreparedStatement(con);
applyStatementSettings(ps);
T result = action.doInPreparedStatement(ps);
handleWarnings(ps);
return result;
}
catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
String sql = getSql(psc);
psc = null;
JdbcUtils.closeStatement(ps);
ps = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw translateException("PreparedStatementCallback", sql, ex);
}
finally {
if (psc instanceof ParameterDisposer) {
((ParameterDisposer) psc).cleanupParameters();
}
JdbcUtils.closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
The "interesting" bit is in the finally block:
JdbcUtils.closeStatement(ps);
This makes it completely impossible to reuse a prepared statement with JdbcTemplate.
It's been a long time (5 years) since I've had occasion to work with Spring JDBC, but I don't recall this ever being a problem. I worked on a large SQL backend with literally hundreds of prepared statements and I clearly remember not having to re-prepare them for every execution.
What I want to do is this:
private static final String sqlGetPDFFile = "select id,root_dir,file_path,file_time,file_size from PDFFile where digest=?";
private PreparedStatement psGetPDFFile;
#Autowired
public void setDataSource(DataSource dataSource) throws SQLException
{
Connection con = dataSource.getConnection();
psGetPDFFile = con.prepareStatement(sqlGetPDFFile);
this.tmpl = new JdbcTemplate(dataSource);
}
...
...
List<PDFFile> files =
tmpl.query(
// PreparedStatementCreator
c -> {
psGetPDFFile.setBytes(1, fileDigest);
return psGetPDFFile;
},
// RowMapper
(rs, n)->
{
long id = rs.getLong(1);
Path rootDir = Paths.get(rs.getString(2));
Path filePath = Paths.get(rs.getString(3));
FileTime fileTime = FileTime.from(rs.getTimestamp(4).toInstant());
long fileSize = rs.getLong(5);
return new PDFFile(id,fileDigest,rootDir,filePath,fileTime,fileSize);
}
);
But of course this fails the second time because of the hardcoded statement close call.
The question: Assuming I want to continue using Spring JDBC, what is the correct way to reuse prepared statements?
Also, if anyone knows why Spring does this (i.e. there's a good reason for it) I'd like to know.
I am abit new with working with DatatBases from Java, and I have been wondering if I am working in a correct way. In my code all the DB interface is done within one class called DataAccess, an example of my code:
Please note that I opened a connection (connBlng) before entering the function isValidOperator().
Is this the correct way to work or should I open and close a connection everytime I need to access the DB?
if(da.StartBlngConnection() == null)
return "ERROR"
DataAccess da = new DataAccess();
da.isValidOperator("123")
//this is code from DataAccess Class
public Boolean isValidOperator(String dn) {
System.out.println( npgReqID + " - " + LOG_TAG + "inside isValidOperator : " + dn);
PreparedStatement prepStmt = null;
ResultSet queryResult = null;
try{
prepStmt = connBlng.prepareStatement("select network_id, network_identifier from operators where network_identifier = ?");
prepStmt.setString(1, dn);
queryResult = prepStmt.executeQuery();
if (queryResult.next()) {
return true;
}
}catch(Exception e){
System.out.println(npgReqID + " - " + e);
DBLog("", new Date(),"" , npgReqID , "" ,"" , MakeSureNotOutOfRange(GetStackTrace(e),4000), "" , "");
return false;
} finally{
closeStatmentandRS(queryResult, PreparedStatement);
}
return false;
}
JDBC plain is not a very easy to use API. Maybe you can take a look at Dalesbread https://github.com/Blackrush/Dalesbred which is a lightwight JDBC wrapper. Here is a discussion about JDBC wrappers in common: simple jdbc wrapper.
I would not suggest to close the DB connection all the time, you should use pooling for that. Normally creating a DB connection is expensive.
I'm trying to create a jax-ws web service that would query a mysql database and return user-defined datatype from the #WebMethod. I first tried returning a sql.ResultSet which is provided by the sql import. However, JAXB couldn't bind it. So I tried returning a String[][], but when reading the result in the client it shows all null fields.
I then tried creating my own data-type and i understand that the class i create should have a default constructor and public sets and gets to all variables defined in that class. But i would like to return a resultset instead. The code to my web service is as follows:
#WebMethod
public String[][] getSummary(int month, int year){
// register jdbc driver
try
{
Class.forName("com.mysql.jdbc.Driver").newInstance();
}
catch(Exception ex)
{
System.out.print(ex.getMessage());
}
// declare connection, statement and result set
Connection connection = null;
Statement statement = null;
ResultSet resultset = null;
String[][] result = new String[30][8];
int counter = 0;
try
{
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/Livestock?user=root");
statement = connection.createStatement();
resultset = statement.executeQuery("SELECT Category, Particulars, Nos, OriginalCost, AccuDep, WDV, NRV, CapLoss FROM TB_SUMMARY WHERE Month=" + month + " AND Year=" + year);
// loop over result set
while(resultset.next())
{
result[counter][0] = resultset.getString("Category");
result[counter][1] = resultset.getString("Particulars");
result[counter][2] = resultset.getString("Nos");
result[counter][3] = resultset.getString("OriginalCost");
result[counter][4] = resultset.getString("AccuDep");
result[counter][5] = resultset.getString("WDV");
result[counter][6] = resultset.getString("NRV");
result[counter][7] = resultset.getString("CapLoss");
counter++;
}
}
catch(SQLException ex)
{
System.out.println("SQLException: " + ex.getMessage());
System.out.println("SQLState: " + ex.getSQLState());
System.out.println("VendorError: " + ex.getErrorCode());
}
finally
{
// it is a good idea to release
// resources in a finally{} block
// in reverse-order of their creation
// if they are no-longer needed
if(resultset != null)
try
{
resultset.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
resultset = null;
if(statement != null)
try
{
statement.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
statement = null;
}
return result;
}
I'm not sure if the reason why all cells in the String[][] are null are because the code doesn't even reach the executeQuery(); or maybe its the jaxb binding that doesn't work.
I still don't understand how jaxb works. If anybody out there has a good example of how to use jaxws with jaxb, I would really appreciate it. The documentations i found in the internet are overwhelming and complicated.
In your scenario you won't be able to return a ResultSet because JAX-WS (JAX-B) is only able to bind simple datatypes. Have a look at this.
Your approach of returning an array is the most common to get over this problem and schould be sufficient in your case.
You should try to debug your application to make sure that your query gets fired and a result gets populated.
My best bet is that you get errors in one of the following lines:
Class.forName("com.mysql.jdbc.Driver").newInstance();
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/Livestock?user=root");
But your code looks fine to me so I'm only speculating. Make sure that your DB is online and try to debug your code.
Have Fun!
I'm trying to delete an event from my table. However I can't seem to get it to work.
My SQL statement is:
public void deleteEvent(String eventName){
String query = "DELETE FROM `Event` WHERE `eventName` ='"+eventName+"' LIMIT 1";
db.update(query);
System.out.println (query);
}
Using MySQL db
Try using the following :
String query = "DELETE FROM `Event` WHERE `eventName` ='"+eventName+"' LIMIT 1";
try {
Connection con = getConnection();
Statement s = con.createStatement();
s.execute(query);
} catch (Exception ex) {
ex.printStackTrace();
}
You have to code your getConnection() method to return a valid Database Connection.
I would suggest using Statement.executeUpdate method, since it returns an integer. So after performing this delete query you will also have information if you really deleted any records (in this case you would expect this method to return 1, since you are using LIMIT=1). I would also suggest closing Statement as soon as you don't need it, here is skeleton implementation:
private void performDelete(Connection conn, String deleteQuery, int expectedResult) throws SQLException {
Statement stmt = conn.createStatement();
int result = -1;
try {
result = stmt.executeUpdate(deleteQuery);
if(result != expectedResult) {
//Here you can check the result. Perhaps you don't need this part
throw new IllegalStateException("Develete query did not return expected value");
}
} catch(SQLException e) {
//Good practice if you use loggers - log it here and rethrow upper.
//Or perhaps you don't need to bother in upper layer if the operation
//was successful or not - in such case, just log it and thats it.
throw e;
} finally {
//This should be always used in conjunction with ReultSets.
//It is not 100% necessary here, but it will not hurt
stmt.close();
}
}