I am learning how to use JDBC inside Spring application but I have some doubt about it.
So, let me explain my doubts about a practial example:
If I have a class that implements my DAO interface and this class contains the following method that insert a new row in a Student table of my database:
public void create(String name, Integer age) {
String SQL = "insert into Student (name, age) values (?, ?)";
jdbcTemplateObject.update(SQL, name, age);
System.out.println("Created Record Name = " + name + " Age = " + age);
return;
}
This method have 2 input parameter: name and age that are related to two column in my database table (NAME and AGE)
Ok...I think that the SQL string rappresent my SQL query that have to be executed to insert the new row in my table
I have some problem to understand what exactly means this piecce of code: values(?, ?)
I think that when I call the update() method on a JdbcTemplate object passing to it the SQL query and the name and *age value these ? placeholder are replaced with these value.
So the query can be executed.
Is it right?
Is it the update() method that replace these placeholder?
I have also read that these placeholder are used so I don't have to worry about escaping my values...what exactly means escape a value ?
And the last question is about the use of PreparedStatment...reading the Spring documentation I have read that I can pass also a PreparedStatment to the update() method...what is the difference? in this case is Spring that create a new PreparedStatment using the SQL string and the 2 input parameter or am I using something different?
It is mainly to avoid SQL injection.
If you just did this:
"insert into Student (name, age) values ('"+name+"', "+age+")"
Consider what would happen if name is "mary', 3), ('john', 13)--"
The query becomes:
insert into Student (name, age) values ('mary', 3), ('john', 13)--', 24)
And when you ignore the SQL comment --:
insert into Student (name, age) values ('mary', 3), ('john', 13)
2 records would be inserted.
You can of course escape or filter the name string, but that is error prone and it's easy to
forget and leave some query vulnerable, might as well use prepared statements to make this impossible.
Escaping means prepending an escape character before any SQL meta character so that its meaning
changes.
If you escaped the name, the query would be:
insert into Student (name, age) values ('\', 3), (\'john\', 13)--', 24)
I.E the name would literally be "', 3), ('john', 13)--" and no SQL injection happened.
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 answers here:
PreparedStatement IN clause alternatives?
(33 answers)
Closed 5 years ago.
Say that I have a query of the form
SELECT * FROM MYTABLE WHERE MYCOL in (?)
And I want to parameterize the arguments to in.
Is there a straightforward way to do this in Java with JDBC, in a way that could work on multiple databases without modifying the SQL itself?
The closest question I've found had to do with C#, I'm wondering if there is something different for Java/JDBC.
There's indeed no straightforward way to do this in JDBC. Some JDBC drivers seem to support PreparedStatement#setArray() on the IN clause. I am only not sure which ones that are.
You could just use a helper method with String#join() and Collections#nCopies() to generate the placeholders for IN clause and another helper method to set all the values in a loop with PreparedStatement#setObject().
public static String preparePlaceHolders(int length) {
return String.join(",", Collections.nCopies(length, "?"));
}
public static void setValues(PreparedStatement preparedStatement, Object... values) throws SQLException {
for (int i = 0; i < values.length; i++) {
preparedStatement.setObject(i + 1, values[i]);
}
}
Here's how you could use it:
private static final String SQL_FIND = "SELECT id, name, value FROM entity WHERE id IN (%s)";
public List<Entity> find(Set<Long> ids) throws SQLException {
List<Entity> entities = new ArrayList<Entity>();
String sql = String.format(SQL_FIND, preparePlaceHolders(ids.size()));
try (
Connection connection = dataSource.getConnection();
PreparedStatement statement = connection.prepareStatement(sql);
) {
setValues(statement, ids.toArray());
try (ResultSet resultSet = statement.executeQuery()) {
while (resultSet.next()) {
entities.add(map(resultSet));
}
}
}
return entities;
}
private static Entity map(ResultSet resultSet) throws SQLException {
Enitity entity = new Entity();
entity.setId(resultSet.getLong("id"));
entity.setName(resultSet.getString("name"));
entity.setValue(resultSet.getInt("value"));
return entity;
}
Note that some databases have a limit of allowable amount of values in the IN clause. Oracle for example has this limit on 1000 items.
Since nobody answer the case for a large IN clause (more than 100) I'll throw my solution to this problem which works nicely for JDBC. In short I replace the IN with a INNER JOIN on a tmp table.
What I do is make what I call a batch ids table and depending on the RDBMS I may make that a tmp table or in memory table.
The table has two columns. One column with the id from the IN Clause and another column with a batch id that I generate on the fly.
SELECT * FROM MYTABLE M INNER JOIN IDTABLE T ON T.MYCOL = M.MYCOL WHERE T.BATCH = ?
Before you select you shove your ids into the table with a given batch id.
Then you just replace your original queries IN clause with a INNER JOIN matching on your ids table WHERE batch_id equals your current batch. After your done your delete the entries for you batch.
The standard way to do this is (if you are using Spring JDBC) is to use the org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate class.
Using this class, it is possible to define a List as your SQL parameter and use the NamedParameterJdbcTemplate to replace a named parameter. For example:
public List<MyObject> getDatabaseObjects(List<String> params) {
NamedParameterJdbcTemplate jdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
String sql = "select * from my_table where my_col in (:params)";
List<MyObject> result = jdbcTemplate.query(sql, Collections.singletonMap("params", params), myRowMapper);
return result;
}
I solved this by constructing the SQL string with as many ? as I have values to look for.
SELECT * FROM MYTABLE WHERE MYCOL in (?,?,?,?)
First I searched for an array type I can pass into the statement, but all JDBC array types are vendor specific. So I stayed with the multiple ?.
I got the answer from docs.spring(19.7.3)
The SQL standard allows for selecting rows based on an expression that includes a variable list of values. A typical example would be select * from T_ACTOR where id in (1, 2, 3). This variable list is not directly supported for prepared statements by the JDBC standard; you cannot declare a variable number of placeholders. You need a number of variations with the desired number of placeholders prepared, or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and JdbcTemplate takes the latter approach. Pass in the values as a java.util.List of primitive objects. This list will be used to insert the required placeholders and pass in the values during the statement execution.
Hope this can help you.
AFAIK, there is no standard support in JDBC for handling Collections as parameters. It would be great if you could just pass in a List and that would be expanded.
Spring's JDBC access supports passing collections as parameters. You could look at how this is done for inspiration on coding this securely.
See Auto-expanding collections as JDBC parameters
(The article first discusses Hibernate, then goes on to discuss JDBC.)
See my trial and It success,It is said that the list size has potential limitation.
List l = Arrays.asList(new Integer[]{12496,12497,12498,12499});
Map param = Collections.singletonMap("goodsid",l);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(getJdbcTemplate().getDataSource());
String sql = "SELECT bg.goodsid FROM beiker_goods bg WHERE bg.goodsid in(:goodsid)";
List<Long> list = namedParameterJdbcTemplate.queryForList(sql, param2, Long.class);
There are different alternative approaches that we can use.
Execute Single Queries - slow and not recommended
Using Stored Procedure - database specific
Creating PreparedStatement Query dynamically - good performance but loose benefits of caching and needs recompilation
Using NULL in PreparedStatement Query - I think this is a good approach with optimal performance.
Check more details about these here.
sormula makes this simple (see Example 4):
ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);
// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);
// select operation for list "...WHERE PARTNUMBER IN (?, ?, ?)..."
for (Inventory inventory: inventoryTable.
selectAllWhere("partNumberIn", partNumbers))
{
System.out.println(inventory.getPartNumber());
}
One way i can think of is to use the java.sql.PreparedStatement and a bit of jury rigging
PreparedStatement preparedStmt = conn.prepareStatement("SELECT * FROM MYTABLE WHERE MYCOL in (?)");
... and then ...
preparedStmt.setString(1, [your stringged params]);
http://java.sun.com/docs/books/tutorial/jdbc/basics/prepared.html
I just started working on a spring-boot application (Java) in MVC. In the controller class,
#GetMapping("/{id}/age")
public ResponseEntity<Integer> getStudentAge(#PathVariable Long id) {
Integer age = studentService.retrieveAgeById(id);
return new ResponseEntity<Integer>(age, HttpStatus.OK);
}
With a simple SQL data, as simple as this:
INSERT INTO student (id, name, age, gender) VALUES (1, 'Rio', 5, 'Male');
When I run the application and check the webpage with path: http://localhost:8080/1/age
I get a response in which age is NOT printed:
Result
The Query used in repository package is:
#Query("select d.id, d.age from Student d where d.id=:id")
Integer findAgeById(Long id);
Also, the requests for student name, gender(Type:String) is successful. But, the request for age (Type:Integer) is not producing similar results.
Adapt your SELECT query to:
#Query("select d.age from Student d where d.id = :id")
Integer findAgeById(#Param("id") Long id);
The query in your question will map the first field from your SELECT to the type of your method, an as you can see, that is your Student ID.
You also could to provide a #Param in your method declaration, because according to this guide:
A query with named parameters is easier to read and is less error-prone in case the query needs to be refactored.
If you want to extact both the ID and the age, you can return your entire Student entity, and use that. Another option is to use a projection but I really don't believe your use-case is advanced enough to benefit from this.
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).
I have an java Object Person with 3 properties firstname, lastname and username.
I have an Oracle stored procedure returning a result set with the 3 columns.
All works fine for that.
Now I have another stored procedure that will only return firstname and lastname but not username.
I get the following error:
Could not read column value from result set username
Hibernate tries to fetch the username property from the resultset.
If I remove the property username, then it works.
My config:
<sql-query name="normalise" callable="true" >
<return alias="val" class="com.nbfg.sipc.model.Person">
<return-property name="firstname" column="FIRST_NAME"/>
<return-property name="lastname" column="LASTNAME_NAME"/>
</return>
{call SCHSIPC.PKG_SIPC_APP.PRC_SIPC_NORMALISE_RS(?, ?, ?, ?, ?) }
</sql-query>
My Pojo (no annotation)
#Entity
public class Person {
private String firstname;
private String lastname;
private String username;
...
The call:
private Value call(String app, String cat, String col, String valeure, String query) {
try {
Query q = getSessionFactory().openStatelessSession().getNamedQuery(query);
q.setString(0, app).setString(1, cat).setString(2, col).setString(3, valeure);
return (Person) q.list().get(0);
} catch (org.hibernate.QueryTimeoutException ex) {
throw new IllegalArgumentException(ex.getCause().getMessage());
}
}
It all works fine if I remove the property username from my Pojo. I can I reuse the same PoJo?
Thank you
Thank you for the code. I am not 100% certain on this, but I believe that with Hibernate SQL queries must return all fields of the object you want the query to instantiate.
An alternative solution you may want to try is to rely on HQL and instantiate the object as needed. For example, SELECT new Person(firstName, lastName) from Person p where p.username = ...
This actually allows you to use any type (with fields of matching types) in your queries.
This would solve your immediate problem, but I am not sure how to address the use of stored procedures in this scenario. Hope this helps.
Well I guest if we are saying that there is no way to tell Hibernate to read only certain Columns in a ResultSet return by a StoreProcedur and that I need to create a separate POJO for each Type of result set, then this the answer.
If this right?