I make a database query and store Account objects in the ResultSet. Here is the code:
try {
ResultSet rs = queryDatabase();
int i=0;
while (rs.next()) {
Account account= rs.getObject(i, Account); //ERROR
accounts.add(account);
i++;
}
} catch (Exception e) {
}
This code returns 3 objects and stores them in the rs. Then I want to get those objects in the ResultSet and put them into an ArrayList as you see in the code. But it gives an error in the specified line saying that ; expected. How can I use getObject method properly?
ResultSet.getObject (and the other getXxx methods) will retrieve the data from the current row of the ResultSet and starts in index 1. You have set your i variable with 0 value.
Just change this
int i=0;
To
int i=1;
Also, getObject needs a single param, but you're incorrectly sending two:
Account account= rs.getObject(i, Account);
Probably you were trying to use ResultSet#getObject(int, Class) (available from Java 7), but you have to take into account that your Account class can't be magically converted from a database column to an instance of this object.
Looks like it would be better to review JDBC trial first, then retry to solve your problem.
Here's another good source to review: Using Customized Type Mappings
Our object:
import java.io.Serializable;
...
class Account implements Serializable{
public String data;
}
How to get our object from bd:
while (rs.next()) {
Object accountJustObject = rs.getObject(i);
Account account = (Account)accountJustObject;
accounts.add(account);
i++;
}
How to save our object:
public void InsertAccount(int id, Account newaccount){
reparedStatement insertNew = conn.prepareStatement(
"INSERT INTO root(id,account) VALUES (?,?)";
insertNew.setInt(1, id); //INT field type
insertNew.setObject(2, newaccount); //OTHER field type
insertNew.executeUpdate();
)
}
Tested under H2 database.
Object Variables, are:
only REFERENCES to a space in memory.
any REFERENCE uses memory (just a little bit)
the way to get/use any object of a specific Type of Class from one reference is by simple Typecasting it.
Related
I have a project of coupons but I have an issue when trying to read a coupon to Eclipse. I have a table of categories which are connected to my coupons table in row "CATEGORY_ID" which is an int. when using add Method I convert my ENUM to int in order to add it to CATEGORY_ID with no problem.
my issue is when trying to read it, I try and convert it to STRING to get a text value, however, I get an exception.
here is my code:
ENUM CLASS:
public enum Category {
FOOD(1), ELECTRICITY(2), RESTAURANT(3), VACATION(4), HOTEL(5);
private Category(final int cat) {
this.cat = cat;
}
private int cat;
public int getIDX() {
return cat;
}
private Category(String cat1) {
this.cat1 = cat1;
}
private String cat1;
public String getName() {
return cat1;
}
}
A Method to add coupon to table COUPONS:
// sql = "INSERT INTO `couponsystem`.`coupons` (`COMPANY_ID`,`CATEGORY_ID`,`TITLE`, `DESCRIPTION`,
`START_DATE`, `END_DATE`, `AMOUNT`, `PRICE`, `IMAGE`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);";
#Override
public void addCoupon(Coupon coupon) throws SQLException {
Connection connection = pool.getConnection();
try {
PreparedStatement statement = connection.prepareStatement(ADD_COUPON);
statement.setInt(1, coupon.getCompanyID());
statement.setInt(2, coupon.getCategory().getIDX());
statement.setString(3, coupon.getTitle());
statement.setString(4, coupon.getDescription());
statement.setDate(5, (Date) coupon.getStartDate());
statement.setDate(6, (Date) coupon.getEndDate());
statement.setInt(7, coupon.getAmount());
statement.setDouble(8, coupon.getPrice());
statement.setString(9, coupon.getImage());
statement.execute();
} finally {
pool.restoreConnection(connection);
}
}
Method to get coupon:
// GET_ONE_COUPON = "SELECT * FROM `couponsystem`.`coupons` WHERE (`id` = ?);";
#Override
public Coupon getOneCoupon(int couponID) throws SQLException {
Connection connection = pool.getConnection();
Coupon result = null;
List<Category> cats = new ArrayList<Category>(EnumSet.allOf(Category.class));
try {
PreparedStatement statement = connection.prepareStatement(GET_ONE_COUPON);
statement.setInt(1, couponID);
ResultSet resultSet = statement.executeQuery();
resultSet.next();
result = new Coupon(resultSet.getInt(1), resultSet.getInt(2), Category.valueOf(resultSet.getString(3)),
resultSet.getString(4), resultSet.getString(5), resultSet.getDate(6), resultSet.getDate(7),
resultSet.getInt(8), resultSet.getDouble(9), resultSet.getString(10));
} finally {
pool.restoreConnection(connection);
}
return result;
on column index (3) I try a and convert ENUM to string to get a text value, here is where I get an exception.
EXCEPTION:
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant coupon.beans.Category.5
at java.base/java.lang.Enum.valueOf(Enum.java:240)
at coupon.beans.Category.valueOf(Category.java:1)
at coupon.dbdao.CouponsDBDAO.getOneCoupon(CouponsDBDAO.java:125)
at coupon.Program.main(Program.java:65)
Hope I am clear with my question. I have no issue adding any more information.
valueOf expects a string that corresponds to the name of the enum element, like "FOOD" but it looks like you pass a number. If you want to pass the id (number) from your enum you need a method to translate between the number and the enum element. Something like this
//in the enum Category
public static Category categoryFor(int id) {
switch (id) {
case 1:
return FOOD;
case 2:
return ELECTRICITY;
//... more case
default:
return HOTEL;
}
}
and then call it like
Category.categoryFor(resultSet.getInt(2))
or you need to store the actual name of the element in your table.
Also you shouldn't use *, "SELECT * ...", in your query but a list of column names so it is clear what column you map in your java code, "SELECT COMPANY_ID, CATEGORY_ID,TITLE,..."
As I understood correctly you're storing the category in your coupon as an enum constant in your code model. While storing it to the database you're mapping it to an integer value with the methods provided by you.
The culprit is in the Category.valueOf(resultSet.getString(3)) method/ part. The Enum.valueOf method is a default method on enums provided by Java and it's working with a String as a parameter - probably therefore you're also using resultSet.getString(3) instead of resultSet.getInt(3) which would have been more intuitive.
From the JavaDoc (which you can find here) it says:
... The name must match exactly an identifier used to declare an enum
constant in this type. (Extraneous whitespace characters are not
permitted.) ...
This means for to get the valueOf method working you need to call it exactly with one of the following values as its arguments: FOOD, ELECTRICITY, RESTAURANT, VACATION, HOTEL. Calling it with the int values like 1, 2, ... or 5 will lead to the IllegalArgumentException you face.
There a two solutions to fix the problem:
Either change your database model to store the enum values/ constants as strings in the table by calling the toString method on the category value before the insert into the database (then your code reading the coupons from the database can stay unchanged).
Or you need to provide your own custom implementation of the "valueOf" method - e.g. findCategoryById - which will work with integer values as its arguments. By writing your own findCategoryById method your code inserting the coupons into the database can remain unchanged.
To implement your own findCategoryById the signature of the method in the Category enum should look like:
public static Category findCategoryById(int index)
Then you can iterate through all available constants by Category.values() and compare the cat with the argument passed to the method and return the matching value based on it.
In case none matches you can simply return null or also throw an IllegalArgumentException. The latter one I'd personally prefer since it follows the "fail fast" approach and can avoid nasty and time consuming search for bugs.
Note: Enums in Java also have an auto generated/ auto assigned ordinal. You can simply request it by calling the ordinal method on a value of your enum. In your case the ordinals are matching the self assigned cat values, so that you could make use of them, instead of maintaining the cat attributes yourself.
When working with the ordinals it's worth mentioning that the order in which you specify your constants in the enum matters! When you change the order of the constants so the ordinals will. Therefore you also need to be careful when working with ordinals. Therefore you might prefer sticking with your current approach (which is not bad at all and widely used), since it avoids the ordering problems ordinals have.
This question already has an answer here:
Getting the data type of a ResultSet column [duplicate]
(1 answer)
Closed 3 years ago.
I was wondering what is the best practice to convert resultSet to String based on user input. I came across a problem which to convert user input from another class.
For an example , i have 2 java class.
query.java & result.java
For result.java. User will key in within the scope to get their desired output. Lets say that there are 2 columns in the database 'name' & 'age'
From my point of view, to make the code neater , i have to classify into 2 different class to make it oop.
result.java
// User will key in datatype as either age or name
public ArrayList<String> result(String datatype){
..
ArrayList<String> data = new ArrayList<String> data;
data = new query.queryArr(datatype);
for(..)..
..
//Database column is
return data
}
At first, i was confused how i can get this method to work
Query.java
public String ArrayList<String> queryArr(String userinput){
ArrayList<String> result = new ArrayList<String>();
..
..
while (rs.next()) {
// The main probelm is, if i return it as age. it will prompt error as the datatype declared in database is integer.
//if it return as integer, this will crash the application as the return type is not the right datatype as it is declared as integer
result.add(rs.getString(userinput));
}
return result;
UPDATE
After realising that the return type can be either integer or string.
I have come to conclusion that the return type should be an object.
public ArrayList<String> recordsQuery(String getRecords){
ArrayList<String> result = new ArrayList<String>();
try{
..
..
ResultSet rs = stmt.executeQuery("select "+ getRecords +" from bills");
while (rs.next()) {
result.add(rs.getObject(1).toString());
}
return result;
}
Do let me know if this is not the best practice to retrieve my data.
JDBC requires a number of default conversions (see table B.6 in the JDBC 4.3 specification). Specifically for getString it lists the following supported JDBC types:
TINYINT, SMALLINT, INTEGER, BIGINT, REAL, FLOAT, DOUBLE, DECIMAL,
NUMERIC, BIT, BOOLEAN, CHAR, VARCHAR, LONGVARCHAR, BINARY, VARBINARY,
LONVARBINARY, DATE, TIME, TIMESTAMP, DATALINK, NCHAR, NVARCHAR,
LONGNVARCHAR
So, calling ResultSet.getString on an INTEGER column should work just fine and the driver should take care of this for you.
In other words, you should be able to unconditionally use
while (rs.next()) {
result.add(rs.getString(1))
}
If this doesn't work, then you should report a bug to the vendor that created this JDBC driver as the implementation doesn't fulfill the requirements of the JDBC specification.
you can see at java doc result set page
Object getObject(int columnIndex)
throws SQLException
List<String> result = new ArrayList<>();
while (rs.next()) {
Object obj = rs.getObject(1);
if(obj instanceOf String)
result.add(rs.getString(1));
else if(obj instanceOf Integer)
// doing something
}
return result;
This is my Model class
//Model
public class CustomerData {
private String locomotive_id;
private String customer_name;
private String road_number;
private String locomotive_type_code;
private String in_service_date;
private String part_number;
private String emission_tier_type;
private String airbrake_type_code;
private String lms_fleet;
private String aar_road;
private String locomotive_status_code;
// Getters and Setters
Here is my RowMapper implementation
//RowMapper
public class CustomerDataResponseMapper implements RowMapper {
#Override
public Object mapRow(ResultSet rs, int count) throws SQLException {
CustomerData customerData = new CustomerData();
customerData.setLocomotive_id(rs.getString("locomotive_id"));
customerData.setCustomer_name(rs.getString("customer_name"));
customerData.setRoad_number(rs.getString("road_number"));
customerData.setLocomotive_type_code(rs.getString("locomotive_type_code"));
customerData.setIn_service_date(rs.getString("in_service_date"));
customerData.setPart_number(rs.getString("part_number"));
customerData.setEmission_tier_type(rs.getString("emission_tier_type"));
customerData.setAirbrake_type_code(rs.getString("airbrake_type_code"));
customerData.setLms_fleet(rs.getString("lms_fleet"));
customerData.setAar_road(rs.getString("aar_road"));
customerData.setLocomotive_status_code(rs.getString("locomotive_status_code"));
return customerData;
}
}
And finally, I got my DaoImpl class here
//DaoImpl
public String getCustomersData(String locoId, String custName, String roadNumber) {
CustomerData resultSet = null;
String str = "";
if (locoId != null && locoId.length() > 0 && !(locoId.equals("0"))) {
str = "select locomotive_id,customer_name,road_number,model_type as locomotive_type_code,to_char(in_service_date,'yyyy-mm-dd') as in_service_date,loco_part_number as part_number, emission_tier_type as emission_tier_type, "
+ "air_brake_type as airbrake_type_code,lms_fleet,aar_road,locomotive_status_code from get_rdf_explorer.get_rdf_locomotive_detail where locomotive_id = ?";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(), locoId);
} else if ((custName != null && custName.length() > 0)
&& (roadNumber != null && roadNumber.length() > 0 && roadNumber != "0")) {
str = "select locomotive_id,customer_name,road_number,model_type as locomotive_type_code,to_char(in_service_date,'yyyy-mm-dd') as in_service_date,loco_part_number as part_number, emission_tier_type as emission_tier_type, "
+ "air_brake_type as airbrake_type_code,lms_fleet,aar_road,locomotive_status_code from get_rdf_explorer.get_rdf_locomotive_detail where customer_name = ? and road_number= ?";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(), custName, roadNumber);
} else {
str = "select distinct customer_name from get_rdf_explorer.get_rdf_locomotive_detail order by customer_name asc";
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper());
}
return resultSet.toString();
}
How can I conditionally get the values from the resultSet based on whether a particular column is present in the resultSet or not. As I am not getting all the columns all the time through my queries.
I am getting SQL bad grammar exception when specific column is not present in resultSet. For example when the third query to get distinct customer names get executed, in the resultSet only customerName would be there, but not the other columns.
It would be really a great help. Thanks a lot in advance.
Since you already have 3 separate queries why not have 3 separate RowMappers, one for each query. Your queries "know" what columns they return, so you can easily create those classes for RowMapper.
If you really want High-Fidelity solution you could create abstract base RowMapper for common parts and 3 subclasses for parts specifig to the query.
You can use a generic method which investigates the ResultSet's columns
#SuppressWarnings("unchecked")
public <T> T getColIfPresent(ResultSet rs, String columnName) throws SQLException {
ResultSetMetaData metaData = rs.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
if (columnName.equals(metaData.getColumnName(i))) {
return (T) rs.getObject(columnName);
}
}
return null;// not present
}
Then, in row mapper.
customerData.setLocomotive_id(getColIfPresent(rs, "locomotive_id"));
...
This has O(n*m) complexity where n - the number of columns checked, m - the number of columns returned in ResultSet.
If you choose to ignore the SQLException, at least log it in DEBUG or TRACE level in case a different subtype of SQLException occurs, so that it's not lost.
Rather than conditionnaly getting columns, you could modify your SQL to match your mapper, like setting other field to empty string or null (I don't remmember if getString() crashes on null or something).
For example your third query would look like:
select distinct customer_name, null as "locomotive_id",'' as "road_number", null as model_type, [etc.] from get_rdf_explorer.get_rdf_locomotive_detail order by customer_name asc
So each query would have the same columns and you don't have to adapt. This is the solution if you d'ont really want/can't change the rowMapper (or want to have only one for this object).
But honestly I would go with ikketu's solution. You should make a separate mapper for the thrid query (plus, it wouldn't be complicated). Not goign with an ORM is a choice but you'll have redundancy problem anyway. I would even add that you should separate some of the logic in your code, this methods seems to be doing different thing (business logic depending on input, and database access) it's not very clear (after the third if, create a method like "getdistinctName()" or something).
Santosh, a quick workaround could be passing a flag to your rowmapper while supplying it to jdbcTemplate. I've done so many times to avoid multiple rowmapper.
resultSet = (CustomerData) jdbcTemplate.queryForObject(str, new CustomerDataResponseMapper(1), custName, roadNumber);
For the above changes, you need to overload constructor with the default one. Then you need to use your flag i.e. instance variable in mapRow() method to handle each situation separately.
You can use BeanPropertyRowMapper which will directly map field names of target class. Here is the javadoc.
The names are matched either directly or by transforming a name separating the parts with underscores to the same name using "camel" case. So, you can use it any other classes whenever you want to map directly to a class. Just have to make sure selected fields are remain in target class. And a default or no-arg constructor.
Following example to get CustomerData using BeanPropertyRowMapper
RowMapper<CustomerData> mapper = new BeanPropertyRowMapper<>(CustomerData.class);
List<CustomerData> result = jdbc.query("your query string...", mapper, query_args...);
So, then you can return first object or whatsoever.
My advice is to split your getCustomersData into three different methods. If you definitely want to ignore this advice, the quick and dirty solution is to protect the rs.getString(...) calls inside your rowMapper. Something like this:
try {
customerData.setLocomotive_id(rs.getString("locomotive_id"));
} catch (SQLException e) {
// ignore this exception. DO NOT TRY THIS AT HOME!
}
If numbers of columns are not fix then you should go with ColumnMapRowMapper based on its implementation even you do not require to create separate concrete class of RowMapper (i.e. CustomerDataResponseMapper ) you just need to pass instance of ColumnMapRowMapper in query as given below:
ColumnMapRowMapper rowMapper = new ColumnMapRowMapper();
List<Map<String, Object>> customerDataList = jdbcTemplate.query(sql,rowMapper, args);
Now you should create one method to manipulate this map like
private CustomerData fillCustomerDataFromMap(List<Map<String, Object>> customerDataList){
CustomerData customerData = new CustomerData();
for(Map<String, Object> map: customerDataList ){
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
customerData.setColumn(map.get("columnName"));
.........
.........
.........
}
return customerData;
}
This is more readable and remove the boilerplate codes and not throw any exception if column name is not present in map (it will simply returns null if column name is not present in map)
Reference of ColumnMapRowMapper :
https://github.com/spring-projects/spring-framework/blob/master/spring-jdbc/src/main/java/org/springframework/jdbc/core/ColumnMapRowMapper.java
Based on the logic of your queries i see that before executing sql query in get_rdf_explorer.get_rdf_locomotive_detail there are some records and 3 options of getting of necessary (unique) record are possible:
by locomotive_id
by customer_name and road_number
ANY record (all records must have same customer_name, else SQL distinct without any conditions return more than 1 row)
So, in the 3rd option you can get any 1 record with all attributes equal to NULL and NOT NULL customer_name value:
str = "select null as locomotive_id, customer_name, null as road_number,
<other attributes> from get_rdf_explorer.get_rdf_locomotive_detail where
rownum = 1";`
I have to do some insertion into my database (SQL Developer) from java.
The information found in my database looks like :
create or replace type shop as object (name varchar2(30),price number(10));
and a table :
create table product (id number, obj shop);
now, when trying to insert into my database from my java code, I have an error like ORA-00932: inconsistent datatypes.
I think this is because of data that I insert.
I've created a function to insert, that has an id and a string.
The problem , i think , is that string, because I need to insert in my "PRODUCT" table, some "SHOP" values.
But i do not know how to insert "SHOP" values from my java code.
My java code looks like :
public class ShopManager {
public void create(Integer ID,String prod) throws SQLException {
Connection con = Database.getConnection();
try (PreparedStatement pstmt = con.prepareStatement("insert into product (id,obj) values (?,?)")) {
pstmt.setInt(1,ID);
pstmt.setString(2, product);
pstmt.executeUpdate();
}
}
And this is how I try to insert :
ShopManager man = new ShopManager();
string manager = "shop(\'Name1\',10)";
man.create(1,manager);
//and here i commit
So, the fact is , that i do not know how to do not insert a STRING from java instead of a shop object that's in my database.
You can use a shop object constructor as part of the insert statement, but you need both a product ID and shop price as numbers, not a combined string:
insert into product (id, obj) values (?, shop(?, ?))
... so the String becomes the second argument you need to set, and you need to decide where the other two argument values are coming from. It looks like you should change your function spec to:
public void create(Integer prodID, String name, Integer shopID)
and then call it as:
man.create(1, "Name1", 10);
although that assumes 'price' will always be am integer, which is probably unlikely, so the third function argument should probably be a float type (with appropriate set call too).
public class Student{
protected String Name;
Protected String Marks;
public Student(String name, String marks){
this.Name=name;
this.Marks=marks;
}
//getters and setters for above
}
From my other class I tried below
Student[] std=new Student[2];
std[0]=new Student("user1","80");
std[1]=new Student("user2","70");
String section = "A";
Connection conn=DriverManager.getConnection("jdbc:sqlserver://....");
CallableStatement stmt= conn.prepareCall("{Call UserDatils(?,?,?)}");
stmt.setString(2, section);
//stmt.setArray(3, std);
stmt.setObject(3, std);
stmt.registerOutParameter(1, java.sql,Types.VARCHAR);
stmt.execute();
Here the issue is with below two lines.
//stmt.setArray(3, std);
stmt.setObject(3, std);
When I run the above with setObject it is saying, "The conversion from UNKNOWN to UNKNOWN is unsupported.
I am not able to set this value.
Please help me to pass above student info to stored procedure.
Thank you.
First, I WILL try to use
std [0] instead of std.
In general if you need to values of one object to another object than you need getters from one object and setter from others.That means you need to know exactly what are the instance members of both objects.Basically you need to know the both the objects involved.
Similarly in the case above when you are passing a Student object in the setObject method JDBC does not know how to convert the Student object to sql object.
JDBC has a set of custom mappings defined for some of the java objects onto corresponding sql types.
Refer docs : http://docs.oracle.com/javase/1.5.0/docs/guide/jdbc/getstart/mapping.html
Hence mapping from any java type to sql type is not possible directly.