I have a question regarding what is the best approach to using stored procs in mysql with hibernate. I am running mysql version 5.7.14 with hibernate 4.0.0.Final as my ORM tool. Now in mysql database, I have a stored proc defined below:
DROP PROCEDURE IF EXISTS LK_spInsertBaseUser;
DELIMITER $$
CREATE PROCEDURE `LK_spInsertBaseUser`(f_name VARCHAR(255),
l_name VARCHAR(255),
n_name VARCHAR(255),
pwd VARCHAR(255),
OUT user_id INT)
BEGIN
## Declaring exit handler
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
#state = RETURNED_SQLSTATE,
#errno = MYSQL_ERRNO,
#message = MESSAGE_TEXT;
SET #full_error = CONCAT('ERROR ', #errno, ' (', #state, '): ', #message);
SELECT #full_error;
ROLLBACK;
END;
START TRANSACTION;
IF NOT EXISTS(SELECT first_name
FROM base_user
WHERE first_name = f_name AND last_name = l_name AND nick_name = n_name)
THEN
INSERT INTO base_user (first_name, last_name, nick_name, password)
VALUES (f_name, l_name, n_name, pwd);
SET user_id = LAST_INSERT_ID();
SELECT #user_id AS userId;
ELSE
SET #exiting_user = CONCAT('Base user already exists');
SELECT #exiting_user;
ROLLBACK;
END IF;
END $$
DELIMITER ;
As we can see from my proc above, if the insert works, the id of the new record is stored in the OUT parameter user_id and we do a select as well. However, if there is a error I print out the error. Now, here is the heart of the question. I ran into a few hiccups when trying to execute the stored proc via hibernate. I finally came up with a solution but I am not convinced it is the right solution. Let me go through the various attempts i went through.
Attempt 1:
I decided to use #NamedNativeQueries annotation for my BaseUser Entity (note: base_user sql table maps to BaseUser pojo entity). Below is the code snippet:
#SqlResultSetMapping(name="insertUserResult", columns = { #ColumnResult(name = "userId")})
#NamedNativeQueries({
#NamedNativeQuery(
name = "spInsertBaseUser",
query = "CALL LK_spInsertBaseUser(:firstName, :lastName, :nickName, :password, #user_id)",
resultSetMapping = "insertUserResult"
)
})
Now, in the Dao class I created a method to invoke the named query via the session object like so:
Query query = getSession().getNamedQuery("spInsertBaseUser")
.setParameter("firstName", user.getFirstName())
.setParameter("lastName", user.getLastName())
.setParameter("nickName", user.getNickName())
.setParameter("password", user.getEncodedPassword());
Object data = query.list();
System.out.println(data);
Now this works partially. It inserts the data into the database however the data object is null. It seems the out parameter isn't set or even retrieved. I then decided to use a different approached and use the CallableStatement object. Below is the code:
Attempt 2:
getSession().doWork((Connection connection) -> {
CallableStatement statement = connection.prepareCall("{call LK_spInsertBaseUser(?, ? , ?, ?, ?)}");
statement.setString(1, user.getFirstName());
statement.setString(2, user.getLastName());
statement.setString(3, user.getNickName());
statement.setString(4, user.getEncodedPassword());
statement.registerOutParameter(5, Types.INTEGER);
statement.executeUpdate();
System.out.println(statement.getInt(5));
});
This works and it is fairly quick however, I have read that the instantiation of the prepareCall is expensive so I guess the real question is, is this solution the acceptable standard or should I continue to figure out the NamedNativeQuery approach in the quest for better performance?
Related
FYI, None of the solutions mentioned in this answer have worked for me. I intend to execute multiple procedure call in one sql query.
The Mysql code is:
SET #SYSTEM_ID = (SELECT `id` FROM `users_admin` WHERE `username`='my_username');
SET #PAYMENT_MODE = 0;
CALL payment_mode_add(#SYSTEM_ID, 'TEST', TRUE, #PAYMENT_MODE);
CALL payment_add(#SYSTEM_ID, #PAYMENT_MODE,
'receipt/00',1000.50,#TEMP_ID);
The way it's supposed to work is, Procedure "payment_mode_add" sets out an output parameter which is supposed to be used as an input parameter by the procedure "payment_add".I know that executing multiple queries at once is not possible in Java, but the method i intend to use here works well in languages like PHP. Definition for payment_mode_add is:
# -- PAYMENT-MODE ADD
DELIMITER //
DROP PROCEDURE IF EXISTS `payment_mode_add` //
# -- remove above
CREATE PROCEDURE `payment_mode_add`(IN _author INT, IN _name VARCHAR(20), IN _active BOOLEAN, OUT _id INT)
BEGIN
# -- declare
IF NOT EXISTS (SELECT `id` FROM `users_admin` WHERE `id`=_author AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Author'
ELSEIF EXISTS (SELECT `id` FROM `gym_form_hhq` WHERE `name`=_name) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '\'Payment Mode\' already exists'
ELSE
SET _active = IFNULL(_active,FALSE)
INSERT INTO `payment_mode`(`name`, `active`, `author`)
VALUES ( _name , _active , _author )
SET _id = LAST_INSERT_ID()
SELECT * FROM `payment_mode` WHERE `id`=_id
END IF
END //
DELIMITER ;
Definition for "payment_add":
# -- PAYMENT ADD
DELIMITER //
DROP PROCEDURE IF EXISTS `payment_add` //
# -- remove above
CREATE PROCEDURE `payment_add`(IN _author INT, IN _mode INT, IN _receipt VARCHAR(50), IN _amount FLOAT, OUT _id INT)
BEGIN
# -- declare
IF NOT EXISTS (SELECT `id` FROM `users_admin` WHERE `id`=_author AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Author'
ELSEIF NOT EXISTS (SELECT `id` FROM `payment_mode` WHERE `id`=_mode AND `active`=TRUE) THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid Payment Mode'
ELSE
SET _receipt = IFNULL(_receipt, NOW())
SET _amount = IFNULL(_amount, 0)
INSERT INTO `payment`(`mode`, `reciept`, `amount`, `author`)
VALUES (_mode , _receipt , _amount , _author)
SET _id = LAST_INSERT_ID()
SELECT * FROM `payment` WHERE `id`=_id
END IF
END
This is just a basic example for some more complex problems that i'm facing in JDBC. Is there any way that i can execute all those 4 queries at once though JDBC or any other method that can give me the output i intend to achieve?
Since this question was getting many upvotes and i had found a solution to it, i'm answering it.
So all you have to do is write a method that takes in an array of string sql queries as its parameter, executes all the queries(in the array) one by one, and returns the last ResultSet. Example:
public ResultSet runMutltipleQueries(String [] arrayOfQueries) throws SQLException{
ResultSet rs = null;
Statement stmnt= con.createStatement();
boolean rsReturned=false;
for(int i=0; i<arrayOfQueries.length;i++){
rsReturned = stmnt.execute(arrayOfQueries[i]);
}
if(rsReturned){
rs= stmnt.getResultSet();
}
return rs;
}
And while calling the method,
queries= new String [] {"SET #SYSTEM_ID = (SELECT `id` FROM `users_admin` WHERE `username`='"+model.getUserName()+"');",
"SET #TRANSACTION_ID = (select `user_data`.`transaction` from `user_data` where id= "+idText.getText()+");",
"SET #FREEZE_ID = NULL;",
// set payment id to 0 for now, update it in pay operation.
"SET #PAYMENT_ID = NULL;",
"CALL user_freeze_add(#SYSTEM_ID, #TRANSACTION_ID, '"+FreezeStartDate.getValue()+"', '"+FreezeStartDate.getValue().plusDays(Integer.parseInt(freezeAvailabletext.getText()))+"', #PAYMENT_ID, #FREEZE_ID);"
};
ResultSet rs= DatabaseHandler.getInstance().runMutltipleQueries(queries);
I am having code something like this.
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
Calculation of fullTableName is something like:
public String getFullTableName(final String table) {
if (this.schemaDB != null) {
return this.schemaDB + "." + table;
}
return table;
}
Here schemaDB is the name of the environment(which can be changed over time) and table is the table name(which will be fixed).
Value for schemaDB is coming from an XML file which makes the query vulnerable to SQL injection.
Query: I am not sure how the table name can be used as a prepared statement(like the name used in this example), which is the 100% security measure against SQL injection.
Could anyone please suggest me, what could be the possible approach to deal with this?
Note: We can be migrated to DB2 in future so the solution should compatible with both Oracle and DB2(and if possible database independent).
JDBC, sort of unfortunately, does not allow you to make the table name a bound variable inside statements. (It has its reasons for this).
So you can not write, or achieve this kind of functionnality :
connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);
And have TUSER be bound to the table name of the statement.
Therefore, your only safe way forward is to validate the user input. The safest way, though, is not to validate it and allow user-input go through the DB, because from a security point of view, you can always count on a user being smarter than your validation.
Never trust a dynamic, user generated String, concatenated inside your statement.
So what is a safe validation pattern ?
Pattern 1 : prebuild safe queries
1) Create all your valid statements once and for all, in code.
Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");
If need be, this creation itself can be made dynamic, with a select * from ALL_TABLES; statement. ALL_TABLES will return all the tables your SQL user has access to, and you can also get the table name, and schema name from this.
2) Select the statement inside the map
String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);
See how the unsafeUserContent variable never reaches the DB.
3) Make some kind of policy, or unit test, that checks that all you statementByTableName are valid against your schemas for future evolutions of it, and that no table is missing.
Pattern 2 : double check
You can 1) validate that the user input is indeed a table name, using an injection free query (I'm typing pseudo sql code here, you'd have to adapt it to make it work cause I have no Oracle instance to actually check it works) :
select * FROM
(select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?
And bind your fullName as a prepared statement variable here. If you have a result, then it is a valid table name. Then you can use this result to build a safe query.
Pattern 3
It's sort of a mix between 1 and 2.
You create a table that is named, e.g., "TABLES_ALLOWED_FOR_DELETION", and you statically populate it with all tables that are fit for deletion.
Then you make your validation step be
conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);
If this has a result, then you execute the safe_table_name. For extra safety, this table should not be writable by the standard application user.
I somehow feel the first pattern is better.
You can avoid attack by checking your table name using regular expression:
if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
}
It's impossible to inject SQL using such a restricted set of characters.
Also, we can escape any quotes from table name, and safely add it to our query:
fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
stmt.setString(1, addressName);
StringEscapeUtils comes with Apache's commons-lang library.
I think that the best approach is to create a set of possible table names and check for existance in this set before creating query.
Set<String> validTables=.... // prepare this set yourself
if(validTables.contains(fullTableName))
{
final PreparedStatement stmt = connection
.prepareStatement("delete from " + fullTableName
+ " where name= ?");
//and so on
}else{
// ooooh you nasty haker!
}
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;
N
10
create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;
TNAME
MYTAB
create or replace procedure deltab(v in varchar2)
is
LvSQL varchar2(32767);
LvChk number;
begin
LvChk := 0;
begin
select count(1)
into LvChk
from TABS2DEL
where tname = v;
if LvChk = 0 then
raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
end if;
exception when others
then raise;
end;
LvSQL := 'delete from '||v||' where n = 10';
execute immediate LvSQL;
commit;
end deltab;
begin
deltab('MYTAB');
end;
select * from mytab;
no rows found
begin
deltab('InvalidTableName');
end;
ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
I have a MySql procedure which contains two update queries for two tables.
The proc works fine when I call it in MySql Workbench. But when I call it through my java code, only one update statement works.
I changed the order in which the two update statements are called but every time, it's the same query that doesn't update. The proc is
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `theProc`(
IN ADMIN_EMAIL_ID_1 varchar(200),
IN IS_EMAIL_ID_1 varchar(100),
IN IS_EMAIL_ID_2 varchar(100),
IN Build_floor varchar(100) ,
IN Locayshan varchar(200),
IN referenceid varchar(100))
BEGIN
UPDATE mySchema.table1 SUMM
SET SUMM.PHY_MAIL_IDS =ADMIN_EMAIL_ID_1, SUMM.swon_flag = Build_floor, SUMM.IS_MAIL_IDS = IS_EMAIL_ID_1, SUMM.IS_MAIL_IDS2 = IS_EMAIL_ID_2
WHERE SUMM.REF_ID=referenceid;/*Works fine*/
UPDATE mySchema.table2 MAS
SET MAS.Location=Locayshan where MAS.REF_ID=referenceid; /*This doesn't work*/
END
And my java code is
String query = "{call mySchema.theProc(?,?,?,?,?,?)}";
cstmt = c.prepareCall(query);
cstmt.setString(1, "a.b#c.com$$f.g#c.com");
cstmt.setString(2, "d.e#c.com");
cstmt.setString(3, "");
cstmt.setString(4, "2nd Floor North Wing$$4th Floor South Wing");
cstmt.setString(5, "California$$Moscow$$");
cstmt.setString(6, "666");
cstmt.executeUpdate();
The string "California$$Moscow$$" is supposed to be stored in table2 but it isn't.
Is there a different way in which I should call the proc? Please help.
i am working with store procedure
i.e
CREATE PROCEDURE test
(
#INPUTPARAM INT,
#OUTPUTPARAM VARCHAR(20)
)
AS
SELECT #OUTPUTPARAM=S.NAME+','+D.NAME
FROM STUDENT S,DEPARTMENT D
WHERE S.DEPTID=D.DEPARTID AND D.DEPARTID=#INPUTPARAM
BEGIN
END
how to get out parameter from java class using hibernate
please share code example
CREATE PROCEDURE test
(
#INPUTPARAM INT,
#OUTPUTPARAM VARCHAR(20) OUTPUT --<-- You need to use key word "OUTPUT" here
)
AS
BEGIN
SELECT #OUTPUTPARAM = S.NAME + ',' + D.NAME
FROM STUDENT S INNER JOIN DEPARTMENT D
ON S.DEPTID = D.DEPARTID --<-- Use New Syntax of join with On Clause
WHERE D.DEPARTID = #INPUTPARAM
END
EXECUTE Procedure
DECLARE #Var VARCHAR(20);
EXECUTE dbo.test
#INPUTPARAM = 1
#OUTPUTPARAM = #Var OUTPUT --<-- use OUTPUT key word here as well
SELECT #Var
The only way to do it is using em.createNativeQuery and talk directly with you DB Server in SQL.
Update:
Here is, how it could be done:
//get connection from em
Session session = (Session)em.getDelegate();
Connection conn = session.connection();
//Native SQL
final CallableStatement callStmt = conn.prepareCall("{call your.function(?)}");
callStmt.setLong(1, documentId);
callStmt.execute();
if (callStmt.getMoreResults()) {
ResultSet resSet = cStmt.getResultSet();
//Do something good with you result
resSet.close();
}
callStmt.close();
//Don't know if calling conn.close(); is a good idea. Since the session owns it.
Hope that helps a little.
Notes:
If you are using JPA 2.0, you can get the session using
Connection conn = em.unwrap(Session.class).connection();
If you are using JPA 2.1, you can call stored procedures directly
StoredProcedureQuery query = em.createNamedStoredProcedureQuery("ReadAddressById");
query.setParameter("P_ADDRESS_ID", 12345);
List<Address> result = query.getResultList();
In PostgreSQL user is a reserved keyword that is used in an internal table, however I also have a separate user table in my own database that I need to use. Whenever I try to execute INSERT or UPDATE statements on the table, it generates the following error: The column name 'id' was not found in this ResultSet.
This is the Java code I am currently using:
PreparedStatement stat1 = conn.prepareStatement("SELECT id FROM user;");
PreparedStatement stat2 = conn.prepareStatement("UPDATE user SET date_created = ? , last_updated = ? , uuid = ? WHERE id = ?;");
ResultSet rs = stat1.executeQuery();
while(rs.next()){
UUID uuid = UUID.randomUUID();
String tempId = uuid.toString();
stat2.setTimestamp(1, curDate);
stat2.setTimestamp(2, curDate);
stat2.setString(3, tempId);
stat2.setLong(4,rs.getLong("id"));
stat2.executeUpdate();
}
So my question is, how could I insert or update the values in my personal user table without interfering with the keyword restriction?
Use this:
prepareStatement("UPDATE \"user\" set date_created = ?")
Or, better yet, rename your user table to something else, like users:
ALTER TABLE "user" RENAME TO users;
Escape the table name like this
select * from "user";