Searching the web for a solution to create a database for my spring project when it doesn't exist, I found this topic here in stackoverflow:
Simulate CREATE DATABASE IF NOT EXISTS for PostgreSQL?
with this stored procedure to acomplish that:
DO
$do$
BEGIN
IF EXISTS (SELECT 1 FROM pg_database WHERE datname = 'mydb') THEN
RAISE NOTICE 'Database already exists';
ELSE
PERFORM dblink_exec('dbname=' || current_database() -- current db
, $$CREATE DATABASE mydb$$);
END IF;
END
$do$
I want run this procedure from my Java code. My initial idea was include this code as a String atribute in this Service class:
#Service
public class InstallService {
private String query = "";
public boolean create_database(String maquina, String usuario, String senha) {
return false;
}
public boolean create_user(String usuario, String senha, String email) {
return false;
}
}
But I just find out this can't be done. Anyone have any sugestion of how to do that inside this class?
I would recommend calling the stored procedure via whatever method you're currently using to connect to Postgres from Spring. (i.e. Spring JDBC: http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/jdbc.html; or perhaps MyBatis, etc.)
You could define the stored procedure in, say, the template database, and then connect to it via Spring JDBC or whatever method you're employing, with a query of the form:
SELECT stored_proc_name(dbname)
(You would need to have the stored procedure take the DB name as an argument, also.)
Edit:
In response to the comment below, inquiring if this can be done without adding anything to a database, the answer is yes:
You could connect to the template1 DB, run the SELECT query against pg_catalog to see if the DB exists (you'll get an empty set back if it doesn't), and if it doesn't exist, run another query on the connection to the template1 db to create the DB.
Basically, it'd be deconstructing the stored procedure into its constituent parts and calling them directly from Java using JDBC or similar.
Related
I am working on an application using Java, Eclipse, and Spring. We have an Oracle database that we are connecting to using JDBC.
Currently the application is using adhoc queries in the application to the database. Most of these were done by people working on the project before I came along. I have decided that using stored procedures is a better way of going. Decouple for another layer of abstraction. Not having to send the sql statement every time so less bandwidth and faster transactions. Oracle can optimize them unlike with the adhoc ones. Changes to them can occur without needing to be recompiled as long as inputs and outputs dont change. All that wonderful stuff.
The adhoc queries frequently get back multiple rows and are using the interface rowMapper and mapRow
return jdbcTemplate.query(sql, new adhoc_mapper1());
class adhoc_mapper1 implements RowMapper<List<String>> {
public List<String> mapRow(ResultSet rs, int arg1) throws SQLException{
ArrayList<String> arr1 = new ArrayList<String>();
arr1.add(rs.getString("OUT_POSITION_ID"));
arr1.add(rs.getString("OUT_POSITION_TITLE_EN"));
return arr1;
}
}
Adhoc Query in Spring
SELECT HR.POSITION_ID, HR.POSITION_TITLE_EN, HR.POSITION_TITLE_FR, HR.SECURITY_ID, HR.GROUP_NAME, HR.GROUP_LEVEL, HR.POSITION_IS_ACTIVE
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.POSITION_ID = DP.POSITION_ID)
WHERE DP.TYPEVALUE = RECORD_TYPE;
Called Procedure in Oracle
CREATE OR REPLACE PROCEDURE DOCSADM.DRILL_RECORD_POSITION (
RECORD_TYPE IN VARCHAR2,
OUT_POSITION_ID OUT VARCHAR2,
OUT_POSITION_TITLE_EN OUT VARCHAR2,
OUT_POSITION_TITLE_FR OUT VARCHAR2,
OUT_SECURITY_ID OUT VARCHAR2,
OUT_GROUP_NAME OUT VARCHAR2,
OUT_GROUP_LEVEL OUT VARCHAR2,
OUT_POSITION_IS_ACTIVE OUT VARCHAR2
) AS
BEGIN
SELECT HR.POSITION_ID, HR.POSITION_TITLE_EN, HR.POSITION_TITLE_FR, HR.SECURITY_ID, HR.GROUP_NAME, HR.GROUP_LEVEL, HR.POSITION_IS_ACTIVE
INTO OUT_POSITION_ID, OUT_POSITION_TITLE_EN, OUT_POSITION_TITLE_FR, OUT_SECURITY_ID, OUT_GROUP_NAME, OUT_GROUP_LEVEL, OUT_POSITION_IS_ACTIVE
FROM HR_POSITION HR JOIN DRILL_POSITION DP ON (HR.POSITION_ID = DP.POSITION_ID) WHERE DP.TYPEVALUE = RECORD_TYPE;
END DRILL_RECORD_POSITION;
As you can see, the procedure returns multiple rows. I had asked a question about how to view the output from the procedure in Oracle but was not successful.
As the project is not using called procedures I have no examples in the code base to work back from. One of my coworkers involved in a different project has used them and was kind enough to show me their example, which regrettably was not helpful because it only called a procedure and had no returns. Their overall design also appears different. But I saw that they were using SimpleJdbcCall so I started looking online to use that.
Good examples online that I failed to get working.
I found examples that were doing what I needed to do, but not quite how I was expecting.
This example goes along the lines of what I was expecting to see and use, except that it only takes a single row as the result and I wasn't able to figure out how to alter the mapping to accept multiple rows.
This example however does use a procedure that returns multiple rows, but it uses ParameterizedBeanPropertyRowMapper and passes in a class.
This example has several ways of doing these calls but are all single row returns, but the answers comments do suggest that its easy to expand for multiple rows. But again I was unable to get it working.
I am not wanting to create a class for every procedure or family of procedures. Most of the queries end up displaying the information in a table, so I have been using a 2d object to hold and then display the data. It has been very convenient so far. I am fine with creating a mapping for each procedure as it needs to be done (unless there is a better way?), but I do not want to create a class for every procedure (and sometimes the mapper as well).
I have 150 lines of failed code attempts which I have not included to keep the question shorter. If they would help then I can include them.
TL;DR
I want to call a stored procedure in Oracle from Spring which has multiple rows being returned. I would like to use just the calling function, which either uses the RowMapper method of mapping, or an in function mapping. I want to avoid using class structures of data if possible. I am expecting/hoping it to look and get used like the first code block.
There is a basic error with my methodology for this. The way in which I was attempting to return multiple rows is wrong. As pointed out by someone in my linked question. So the ways that I was attempting to access it were also wrong. When I limited the return to a single row the following was successful.
First the imports
import java.sql.Connection;
import java.sql.CallableStatement;
Then the call
final String procedureCall = "{call DRILL_RECORD_POSITION(?, ?, ?, ?, ?, ?, ?, ?)}";
try (Connection connection = jdbcTemplate.getDataSource().getConnection();)
{
ArrayList<String> inner = new ArrayList<String>();
CallableStatement callableSt = connection.prepareCall(procedureCall);
callableSt.setString(1, "D");
callableSt.registerOutParameter(2, Types.VARCHAR);
callableSt.registerOutParameter(3, Types.VARCHAR);
callableSt.registerOutParameter(4, Types.VARCHAR);
callableSt.registerOutParameter(5, Types.VARCHAR);
callableSt.registerOutParameter(6, Types.VARCHAR);
callableSt.registerOutParameter(7, Types.VARCHAR);
callableSt.registerOutParameter(8, Types.VARCHAR);
//Call Stored Procedure
callableSt.executeUpdate();
for (int i=2; i<9; i++)
{
System.out.println(callableSt.getString(i));
}
}catch (SQLException e) {
e.printStackTrace();
}
Note that I am using the try with resources so I do not have to worry about closing the connection.
Other mistakes I made during this was not having enough ? in the String which was leading to invalid column index errors. I then made the mistake of trying to get the information before I actually executed the call.
At the first stage i'm managing my app via JDBC, so i'm the resposible to build and validate all the SQL. But i was wondering if JPA could give me a hand on these tasks.
So, at this moment i've already have a DbaUser model, which was generated from the DBA_USERS table on the OracleDB, and i can actually list all of them.
However, i'm trying to manage to create or update more, but whenever i try to create using
em.createQuery("CREATE USER C##ANTONIO IDENTIFIED BY Antionio123").executeUpdate();
An syntax exception is launched: The query does not start with a valid identifier, has to be either SELECT, UPDATE or DELETE FROM.
Could you guys enlighten me a bit more or pointing me to some proper tutorials? I've been googling but nothing concrete apprears on Oracle DBs system tables.
Update1 (Thanks to #JB Nizet)
After replacing the execution of the query from JPQL to Native SQL, i've got an error such as:
Query: DataModifyQuery(sql="CREATE USER C? IDENTIFIED BY ANTONIO123").
I've replaced the hashtags with a scape character "...C##..." with "...C\#\#..." but the issue earns a different flavour:
Query: DataModifyQuery(sql="CREATE USER C\? IDENTIFIED BY ANTONIO123")
... and i really need to send the "##" to the Oracle DB. How can i force these special characters?
Update 2
So...after googling a bit more, i've found out positional parameters, and i've also discovered that we cannot have named paramteres on JPA native queries. After this, i've tried:
em.createNativeQuery("CREATE USER ?1 IDENTIFIED BY ANTONIO123").setParameter(1, "C##ANTONIO").executeUpdate();
Which triggers: ORA-01935: missing user or role name
alongside with
Error Code: 1935
Call: CREATE USER ? IDENTIFIED BY ANTONIO123
Which tells me that this binding doesn't work. Is there another way to do it?
Kind regards and thanks in advance,
Sammy
createQuery() expects a JPQL query. What you passed is not JPQL. It's SQL.
Use createNativeQuery() to execute SQL.
To create a common user (prefixed with C##) you (i.e. your JPA connection pool user) need a specific priviledges.
CREATE ROLE and
SET CONTAINER
Those are not a typical privileges granted to a JPA connection, so I'm guessing you will fail with the creation of a new common user.
Additionally you need to be connected to the root container.
The further example are plain JDBC called from Groovy, it should be easy possible to pass it to JPA if you get the DB connection.
def stmt = con.prepareStatement("SELECT SYS_CONTEXT('USERENV', 'CON_NAME') CON_NAME FROM dual")
def rs = stmt.executeQuery()
while(rs.next())
{
println "container name= ${rs.getString('CON_NAME')}"
}
gives
container name= CDB$ROOT
Note that if you are connected to a local DB, you get an error while trying to create a user prefixed with C##
ORA-65094: invalid local user or role name
If both conditions are fulfilled, it is possible to create / drop the common user:
String cu = "create user \"C##TEST\" identified by password123 profile \"DEFAULT\" account unlock"
con.createStatement().execute(cu)
resp.
cu = "drop user \"C##TEST\""
con.createStatement().execute(cu)
Finally should be stated, that this exercise was done for the aim of completeness only. I do not see a real use case for a JPA pool connection to be granted such privileges and connecting the root container. The database maintenance is typically done not using JPA.
Another option would be to create a stored procedure in the database and then invoke stored procedure from JPA, thus you do not need to bother about caveats and syntax.
E.g.assume that JPA provider is EclipseLink
Database stored procedure
CREATE OR REPLACE PROCEDURE p_user_creation (p_username IN VARCHAR2,
p_password IN VARCHAR2,
p_return OUT NUMBER)
IS
v_syntax VARCHAR2 (256);
BEGIN
IF (p_username IS NOT NULL)
THEN
v_syntax :=
'CREATE USER '''
|| p_username
|| ''' IDENTIFIED BY '''
|| p_password
|| '''';
EXECUTE IMMEDIATE v_syntax;
p_return := 0;
END IF;
EXCEPTION
WHEN OTHERS
THEN
raise_application_error (-20002, 'An error has occurred!');
END;
Java code snippet to invoke stored procedure
try {
Integer returnValue = null;
StoredProcedureQuery storedProcedureQuery =
getEntityManager().createStoredProcedureQuery("p_user_creation");
storedProcedureQuery.registerStoredProcedureParameter("p_username", String.class, ParameterMode.IN);
storedProcedureQuery.registerStoredProcedureParameter("p_password", String.class, ParameterMode.IN);
storedProcedureQuery.registerStoredProcedureParameter("p_return", Integer.class, ParameterMode.OUT);
storedProcedureQuery.setParameter("p_username", "SCOTT");
storedProcedureQuery.setParameter("p_password", "tiger");
storedProcedureQuery.execute();
returnValue = (Integer) storedProcedureQuery.getOutputParameterValue("p_return");
} catch (Exception e) {
log.error("Error " + e.getMessage());
}
I have a web application in which a user must log into. I am using Hibernate to save objects to the MySQL database. In the database i have triggers which insert new records into audit tables when ever an insert/update/delete is done on an object
DELIMITER $$
CREATE TRIGGER `trg_insert_Book` BEFORE INSERT ON Book FOR EACH ROW
BEGIN
INSERT INTO audit_book (AUDITTYPE,AUDITDATE,bookId, bookName, pages) VALUES ('I',NOW(), NEW.bookId, NEW.bookName, NEW.pages);
END; $$
DELIMITER ;
As you can see from the trigger above i am inserting a new record into the audit_book table when ever an insert is done on the Book table. The insert is done using the following code
public void addNewBook(Book book) {
Session session = sessionFactory.getCurrentSession();
session.save(book);
}
I have a method which returns the user that is currently using the system
#ModelAttribute("username")
public String getPerson(Principal principal) {
return principal.getName();
}
My question is how can i pass this username to the database so that the username is added to the audit table without adding the username to the Book object?
No, you can't do it that way, because the database is not aware of the logic about identifying application users.
You could consider using Hibernate Envers instead of database triggers for auditing purposes. In the linked example you can see how to accomplish your goal in the section 4. Add username information to each revision.
DELIMITER //
CREATE PROCEDURE temp ( empId INT)
BEGIN
DECLARE var_etype VARCHAR(36);
SELECT
emptype = QOUTE(emptype)
FROM
dms_document
WHERE
id = empid;
SELECT
emptype,
CASE
WHEN emptype = 'P' THEN doctype
ELSE 'No Documents required'
END
FROM
dms_report
WHERE
pilot = 1;
End//
DELIMITER ;
I have created this procedure successfully but when I try to call it, I am getting error 1305 the function database.temp does not exist. I am trying to call using this statement:
SET #increment = '1';
select temp( #increment)
but I get Error, please tell me where I made mistake.
This is how you call it, use use the keyword call and then procedure's name
call procedureName(params);
in call of making an string
String sqlString = "procedureName("+?+")"; //in case of Integers
String sqlString = "procedureName('"+?+"')";//in case of Integers
bring the parameter in prepared statement.
MySQL's documentation on Using JDBC CallableStatements to Execute Stored Procedures explains the necessary steps quite well.
This is what your java code needs to look like:
CallableStatement cStmt = conn.prepareCall("{call temp(?)}");
cStmt.setInt(1, 42); //set your input parameter, empId, to 42.
If you want to work with the rows returned by your stored procedure's query in your Java code, you're also going to need to create an OUT parameter as noted in MySql's documentation page titled, CALL Syntax:
CALL can pass back values to its caller using parameters that are
declared as OUT or INOUT parameters
In order to call your stored procedure from MySQL workbench, use the CALL command. You can call stored procedure by directly setting values for each of the parameters:
SET #increment = 1;
CALL temp(#increment)
Then you simply use the SELECT statement to return the value of your output parameter
SELECT #outParameter
With help setting your output parameters, please read the article MySQL Stored Procedure - SELECT - Example.
Your stored procedure is syntactically wrong, and as mentioned in the comments, you're not using the stored procedure functionality for it's intended use. It's intended to be used for data manipulation not for querying. You should instead consider turning your procedure into a series of prepared statements.
Please let me know if you have any questions!
I am working on the web application project developed using java. In my working project,
i have the requirement like i need to create the database dynamically after the user has been registered.I had done that approach.
But, now i want to call one stored procedure that is available in another schema(Master DB).The stored procedures contains tables. Now, i want to call that procedure in dynamically created DB.
I have written the code like following, can anybody help me to know what's wrong in this code,
Connection c1 = DriverManager.getConnection(URL, USER, PASSWORD);
java.sql.CallableStatement cstmt=null;
System.out.println("Invoking the stored procedure from subscription DB........");
String callSP="{call masterdb.createCorporateDBProc()};";
cstmt= c1.prepareCall(callSP);
cstmt.execute();
java.sql.CallableStatement cstmt=null;
try {
System.out.println("Invoking the stored procedure from subscription DB........");
String callSP="{call subscription.createCorporateDBProc()}";
cstmt = c1.prepareCall(callSP);
int r = cstmt.executeUpdate();
System.out.println("SP created"+r);
System.out.println("SP invoked and executed successfully in corporate DB....");
} catch(com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException e){
e.printStackTrace();
cstmt.close();
c1.close();
}
See javadoc for Statement:
http://docs.oracle.com/javase/7/docs/api/java/sql/Statement.html#executeUpdate%28java.lang.String%29
Returns:
either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements that return nothing
This means that execute for procedure will return 0. Check your database as well, if the call was successful.