So, I was using a datasource which was defined in Spring, which was working fine. Then I updated my project to take the datasource from the Weblogic server which the application is running on. This too, works fine for most calls to the database, except for one scenario - This scenario is involved sending a list of objects to the database, based on database types which are defined in Java by using Structs.
The full method is:
#Override
public List<String> saveAllocation(String originalId, List<Parcel> parcels) throws SQLException {
if(originalId == null || parcels == null) {
return null;
}
List<String> results = null;
String result = null;
String log = null;
OracleConnection oracleConnection = (OracleConnection)jdbcTemplate.getDataSource().getConnection();
try {
OracleCallableStatement cs = (OracleCallableStatement) oracleConnection.prepareCall("{ call PACKAGE.Update(?, ?, ?, ?) }");
Struct[] cpoList = new Struct[parcels.size()];
for(int i = 0; i < parcels.size(); i++) {
Object[] obj = new Object[] { parcels.get(i).getParcel_id(), parcels.get(i).getPublicID().toUpperCase() };
Struct struct = oracleConnection.createStruct("SCHEME_NAME.PARCEL_OBJ", obj);
cpoList[i] = struct;
}
Array array = oracleConnection.createARRAY("SCHEME_NAME.PARCEL_TAB", cpoList);
cs.setString(1, originalId);
cs.setArray(2, array);
cs.registerOutParameter(3, Types.VARCHAR);
cs.registerOutParameter(4, Types.VARCHAR);
cs.executeUpdate();
log = cs.getObject(3).toString();
result = cs.getObject(4).toString();
results = new ArrayList<>();
results.add(result);
results.add(log);
} catch(SQLException e) {
//Log exception
return results;
} catch(Exception e) {
//Log exception
return results;
} finally {
if (cs != null) {
cs.close();
}
}
return results;
}
}
The database objects are defined as:
PARCEL_OBJ
create or replace TYPE parcel_obj AS OBJECT
(PARCEL_ID VARCHAR2(11),
PUBLIC_ID VARCHAR2(20));
PARCEL_TAB
create or replace TYPE parcel_tab IS TABLE OF parcel_obj;
The application fails on the line
Array array = oracleConnection.createARRAY("SCHEME_NAME.PARCEL_TAB", cpoList);
The exception message is:
java.sql.SQLException: Fail to convert to internal representation: weblogic.jdbc.wrapper.Struct_oracle_sql_STRUCT#187>
My JNDI connection is defined in my application.properties like:
spring.datasource.jndi-name=jdbc/pio
Any help would be appreciated!
As the documentation mentions.
By default, data type objects for Array, Blob, Clob, NClob, Ref,
SQLXML, and Struct, plus ParameterMetaData and ResultSetMetaData
objects are wrapped with a WebLogic wrapper.
In some cases setting the wrapping parameter to false can improve significantly the performance and allows the application to use a native driver.
I don't see a problem disabling that option since it is causing the problem when calling objects like struct in the first place.
Related
So essentially, I am using java to obtain information, and then I am using Kotlin to manage the information. So what I have done so far is, I have stored my information into a ArrayList called tableData in java, I store all my elements into this list (I should have used a better name here) and then returned the list. My java code:
public static ArrayList<String> readAllData () {
//Connecting to database
Connection con = DbConnection.connect();
//Preparing statement
PreparedStatement ps = null;
//result set declaration
ResultSet rs = null;
//tableData String array
ArrayList<String> tableData = new ArrayList<String>();
try {
//Database initialization
String sql = "SELECT * FROM ProjectInfo";
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
//for each iteration store the data into variable a from column projectName
String a = rs.getString("projectName");
//print out each element
//System.out.println("a = " + a);
tableData.add(a);
}
//other catch exceptions
} catch (SQLException e) {
System.out.println(e.toString());
} finally {
try {
rs.close();
ps.close();
con.close();
} catch (SQLException e){
System.out.println(e.toString());
}
}
//System.out.println(tableData);
//return all the data that has been stored into this array
return tableData;
}
In Kotlin, I created a property class called GettingData and passed one parameter projectName: ArrayList<String>. Then i moved onto actually printing out the data
class GettingData(var projectName: ArrayList<String>) {
}
fun ManageData() {
var arrayData = listOf<GettingData>(GettingData(DbConnection.readAllData()))
var projectNameData = arrayData.map {it.projectName}
for (projectName in projectNameData) {
println(projectName)
}
}
All my elements are printed out, however I cannot use the filter functions to call specific elements from the arrayList? I want to be able to call every element and print them out in a alphabetical order? I tried filter, sortedWith and find functions but I cannot seem to get it working. How can I achieve this?
I think your question boils down to wanting to print a list of strings in alphabetical order.
You can use the sorted() function:
for (projectName in projectNameData.sorted()) {
println(projectName)
}
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 have a problem with loading objects from a SQLite database.
First of all, this is my table definition:
CREATE TABLE MyTable (
rowid INTEGER PRIMARY KEY,
data BLOB
);
This is the simple class which I want to store and reload:
public class MyHashMap extends HashMap<String, Integer> {
private static final long serialVersionUID = 0L;
}
Then I'm filling the map with some data and store it with an SQL INSERT statement in the database. Everything works fine and if I execute a SELECT (with the sqlite3 command-line client) I will see the correct information.
Now I'm using the java.sql package to load the object:
String sql = "SELECT data FROM MyTable WHERE rowid = 1";
MyHashMap map = null;
try {
try (Statement stmt = db.createStatement()) {
try (ResultSet rs = stmt.executeQuery(sql)) {
if (rs.next()) {
map = rs.getObject("data", MyHashMap.class);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
There's no exception thrown but my map variable is null. I debugged the program and I can say that the getObject method is called as expected.
First, you definition of MyHashMap is incorrect
public class MyHashMap extends HashMap<Integer, String> {
private static final long serialVersionUID = 0L;
}
The main issue, though, is that SQL doesn't store Java objects; it merely stores rows of records, which consist of fields. You need to read these records one by one, and store them in your map. Roughly as follows:
MyHashMap map = new MyHashMap();
final String sql = "SELECT rowid, data FROM MyTable";
try (final Statement stmt = connection.createStatement;
final ResultSet rs = stmt.executeQuery(sql)) {
while (rs.next()) {
map.put(rs.getInt(1), rs.getString(2));
}
}
Please note that there's a good chance that reading a Blob into a String will fail. Usually, JDBC drivers are clever enough to convert data types, but if you have raw binary data in your blob, you cannot read it into a string. You would need the getBlob method instead, and deal with the resulting object. But I can't tell from your code what you'll be storing in that blob.
Ok, I found a solution with the following method:
private Object getObjectFromBlob(ResultSet rs, String columnName)
throws ClassNotFoundException, IOException, SQLException {
InputStream binaryInput = rs.getBinaryStream(columnName);
if (binaryInput == null || binaryInput.available() == 0) {
return null;
}
ObjectInputStream in = new ObjectInputStream(binaryInput);
try {
return in.readObject();
} finally {
in.close();
}
}
I am trying to execute a stored procedure using SQL Server JDBC in a method:
//Connection connection, String sp_name, Map<String, Object>params input to the method
DatabaseMetaData dbMetaData = connection.getMetaData();
HashMap<String, Integer> paramInfo = new HashMap<String, Integer>();
if (dbMetaData != null)
{
ResultSet rs = dbMetaData.getProcedureColumns (null, null, sp_name.toUpperCase(), "%");
while (rs.next())
paramInfo.put(rs.getString(4), rs.getInt(6));
rs.close();
}
String call = "{ call " + sp_name + " ( ";
for (int i = 0; i < paramInfo.size(); i ++)
call += "?,";
if (paramInfo.size() > 0)
call = call.substring(0, call.length() - 1);
call += " ) }";
CallableStatement st = connection.prepareCall (call);
for (String paramName: paramInfo.keySet()){
int paramType = paramInfo.get(paramName);
System.out.println("paramName="+paramName);
System.out.println("paramTYpe="+paramType);
Object paramVal = params.get(paramName);
st.setInt(paramName, Integer.parseInt(((String)paramVal))); //All stored proc parameters are of type int
}
Let say the stored procedure name is ABC and parameter is #a.
Now DatabaseMetaData returns column name #a but setting st.setInt("#a",0) returns following error:
com.microsoft.sqlserver.jdbc.SQLServerException: Parameter #a was not defined for stored procedure ABC.
Instead, I tried this: st.setInt("a",0) and it executed perfectly.
Now the problem is I have to set the parameters dynamically as I have too many stored procedures and too many parameters but jdbc is giving error.
Edit 1:
As pointed out in one answer that my question is a duplicate of: Named parameters in JDBC, I would like to explain that the issue here is not named parameters or positional ones, rather it is about JDBC not handling the SQL server parameters itself properly or my making some error while invoking it.
Update 2017-10-07: The merge request to fix this issue has been accepted, so this should no longer be a problem with versions 6.3.4 and later.
Yes, it is an unfortunate inconsistency that for mssql-jdbc the parameter names returned by DatabaseMetaData#getProcedureColumns do not match the names accepted by CallableStatement#setInt et. al.. If you consider it to be a bug then you should create an issue on GitHub and perhaps it will be fixed in a future release.
In the meantime, however, you'll just have to work around it. So, instead of code like this ...
ResultSet rs = connection.getMetaData().getProcedureColumns(null, "dbo", "MenuPlanner", null);
while (rs.next()) {
if (rs.getShort("COLUMN_TYPE") == DatabaseMetaData.procedureColumnIn) {
String inParamName = rs.getString("COLUMN_NAME");
System.out.println(inParamName);
}
}
... which produces ...
#person
#food
... you'll need to use code like this ...
boolean isMssqlJdbc = connection.getClass().getName().equals(
"com.microsoft.sqlserver.jdbc.SQLServerConnection");
ResultSet rs = connection.getMetaData().getProcedureColumns(null, "dbo", "MenuPlanner", null);
while (rs.next()) {
if (rs.getShort("COLUMN_TYPE") == DatabaseMetaData.procedureColumnIn) {
String inParamName = rs.getString("COLUMN_NAME");
if (isMssqlJdbc && inParamName.startsWith("#")) {
inParamName = inParamName.substring(1, inParamName.length());
}
System.out.println(inParamName);
}
}
... which produces ...
person
food
In my situation, I am querying a database for a specific return (in this case registration information based on a username).
//Build SQL String and Query Database.
if(formValid){
try {
SQL = "SELECT * FROM users WHERE username=? AND email=?";
Collections.addAll(fields, username, email);
results = services.DataService.getData(SQL, fields);
if (!results.next()){
errMessages.add("User account not found.");
} else {
user = new User();
user.fillUser(results); //Is it ok to pass ResultSet Around?
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
services.DataService.closeDataObjects(); //Does this close the ResultSet I passed to fillUser?
}
}
So once I query the database, if a result is found I create a new User object and populate it with the data I received from the database. I used to do all of this directly in the method that I was pulling the resultset into, but I realized I was doing a lot of redundant coding throughout my project so I moved it all into one central method that lives in the actual User bean.
public void fillUser(ResultSet data) throws SQLException{
setUserId(data.getInt("id"));
setFirstName(data.getString("first_name"));
setLastName(data.getString("last_name"));
setUsername(data.getString("username"));
setType(data.getString("type"));
setEmail(data.getString("email"));
}
I have done a few tests and from what I can determine, because I close the original resultset in the finally block of the query, the resultset that I pass into the fillUser method also gets closed. Or am I wrong and am I seriously leaking data? This is actually the second time I pass a resultset (so its two instances of one) because the block I use to query my database is
public static ResultSet getData(String SQL, ArrayList fields) throws SQLException {
try{
connection = Database.getConnection();
preparedStatement = connection.prepareStatement(SQL);
for(int i=0; i<fields.size(); i++){
Integer num = i + 1;
Object item = fields.get(i);
if(item instanceof String){
preparedStatement.setString(num, (String) item); //Array item is String.
} else if (item instanceof Integer){
preparedStatement.setInt(num, (Integer) item); //Array item is Integer.
}
}
resultSet = preparedStatement.executeQuery();
return resultSet;
}finally{
}
}
All of these code snippets live in separate classes and are reused multiple times throughout my project. Is it ok to pass a resultset around like this, or should I be attempting another method? My goal is to reduce the codes redundancy, but i'm not sure if i'm going about it in a legal manner.
Technically, it's OK to pass result sets, as long as you are not serializing and passing it to a different JVM, and your JDBC connection and statement are still open.
However, it's probably a better software engineer and programming practice to have DB access layer that returns you the result set in a Java encoded way (a list of User in your example). That way, your code would be cleaner and you won't have to worry if the ResultSet is already opened, or you have to scroll it to the top, you name it...
As everyone before me said its a bad idea to pass the result set. If you are using Connection pool library like c3p0 then you can safely user CachedRowSet and its implementation CachedRowSetImpl. Using this you can close the connection. It will only use connection when required. Here is snippet from the java doc:
A CachedRowSet object is a disconnected rowset, which means that it makes use of a connection to its data source only briefly. It connects to its data source while it is reading data to populate itself with rows and again while it is propagating changes back to its underlying data source. The rest of the time, a CachedRowSet object is disconnected, including while its data is being modified. Being disconnected makes a RowSet object much leaner and therefore much easier to pass to another component. For example, a disconnected RowSet object can be serialized and passed over the wire to a thin client such as a personal digital assistant (PDA).
Here is the code snippet for querying and returning ResultSet:
public ResultSet getContent(String queryStr) {
Connection conn = null;
Statement stmt = null;
ResultSet resultSet = null;
CachedRowSetImpl crs = null;
try {
Connection conn = dataSource.getConnection();
stmt = conn.createStatement();
resultSet = stmt.executeQuery(queryStr);
crs = new CachedRowSetImpl();
crs.populate(resultSet);
} catch (SQLException e) {
throw new IllegalStateException("Unable to execute query: " + queryStr, e);
}finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
LOGGER.error("Ignored", e);
}
}
return crs;
}
Here is the snippet for creating data source using c3p0:
ComboPooledDataSource cpds = new ComboPooledDataSource();
try {
cpds.setDriverClass("<driver class>"); //loads the jdbc driver
} catch (PropertyVetoException e) {
e.printStackTrace();
return;
}
cpds.setJdbcUrl("jdbc:<url>");
cpds.setMinPoolSize(5);
cpds.setAcquireIncrement(5);
cpds.setMaxPoolSize(20);
javax.sql.DataSource dataSource = cpds;