I am struggling with the below code to make it work, searching documentation and forums and stucked.
Finally I decided to ask you for help.
What I have is package with TYPES, FUNCTION declarations and FUNCTION BODY declaration.
In future I would like to use SYNONYM to MYPACKAGE (This is only mock - I will not have package and types declarations in my database, but use dblink to external database and Java code to run procedures / functions, but now I don't have this dblink accessible) and MYPACKAGE will be something accessible through dblink:
create public synonym dblink_MYPACKAGE for SOME_SCHEMA.MYPACKAGE#dblink_externalDB;
and I will be using dblink_MYPACKAGE instead of MYPACKAGE in Java Code.
(but this doesn't matter does it?) The external database is not ours, so we CAN'T change anything there...
public class TestClassSpringBased {
private DataSource dataSource;
private SimpleJdbcCall jdbcCall;
#Override
public void testMe(Integer id) {
int iid = 1;
SqlParameterSource in = new MapSqlParameterSource().addValue("IN_1", iid);
Map<String, Object> out = jdbcCall.execute(in);
}
public DataSource getDataSource() {
return dataSource;
}
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.jdbcCall = new SimpleJdbcCall(dataSource)
.withCatalogName("MYPACKAGE")
.withProcedureName("MYFUNCTION")
.withReturnValue()
.useInParameterNames("IN_1")
.declareParameters(
new SqlInOutParameter("IN_1", OracleTypes.NUMBER),
new SqlInOutParameter("OUT_1", OracleTypes.STRUCT, "MYPACKAGE.CUSTOMELEMENTSTYPE",
new SqlReturnType() {
public Object getTypeValue(CallableStatement cs, int colIndx, int sqlType,
String typeName) throws SQLException {
return null; //just let it work, the I will think what to write here
}
}));
}
}
create or replace
PACKAGE MYPACKAGE IS
TYPE CUSTOMELEMENTSTYPE_R IS RECORD (
C1 VARCHAR2(60),
C2 VARCHAR2(30)
);
TYPE CUSTOMELEMENTSTYPE IS TABLE OF CUSTOMELEMENTSTYPE_R
INDEX BY PLS_INTEGER;
FUNCTION MYFUNCTION(
IN_1 IN INTEGER, OUT_1 OUT CUSTOMELEMENTSTYPE )
RETURN VARCHAR2;
END;
create or replace
PACKAGE BODY MYPACKAGE IS
FUNCTION MYFUNCTION(
IN_1 IN INTEGER, OUT_1 OUT CUSTOMELEMENTSTYPE )
RETURN VARCHAR2 IS
BEGIN
SELECT * BULK COLLECT INTO OUT_1
FROM SOME_TABLE;
RETURN 'return param';
END MYFUNCTION;
END MYPACKAGE ;
The ERROR is:
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [{? = call MYPACKAGE.MYFUNCTION(?, ?)}]; SQL state [99999]; error code [17074]; invalid name pattern: MYPACKAGE.CUSTOMELEMENTSTYPE; nested exception is java.sql.SQLException: invalid name pattern: MYPACKAGE.CUSTOMELEMENTSTYPE
The problem is only with OUT parameter, the same code works, when I dont pass OUT parameter and run it against another version of MYFUNCTION, that has not OUT parameter.
I tried also with OracleTypes.ARRAY (invalid name pattern) and OracleTypes.OTHER (Caused by: java.sql.SQLException: wrong column type: 1111)
It seems that You use incorrect method call:
Your code:
.withProcedureName("MYFUNCTION")[..]
should be replaced by
.withFunctionName[...]
here is some simple examle of whole function call:
JdbcTemplate jdbc = new JdbcTemplate(txManager.getDataSource());
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbc)
.withCatalogName("p_adm_www")
.withFunctionName("fn_usr_get_login_sequence")
.declareParameters(new SqlOutParameter("RETURN", OracleTypes.NUMBER))
.withoutProcedureColumnMetaDataAccess();
jdbcCall.setAccessCallParameterMetaData(false);
BigDecimal returnId = jdbcCall.executeFunction(BigDecimal.class, null);
return returnId.longValue();
Related
I am making a discussion board with Spring.
I am using JdbcTemplate to populate the articles of the users from the database, but the JdbcTemplate's query method does not return the proper ResultSet. Interestingly, when I copy and paste the SQL query from the code to SQL Developer, it returns the proper results.
The photo that shows the SQL query works,
JdbcTemplate code
public class ForumDao {
private JdbcTemplate template;
public ForumDao(DataSource dataSource) {
template = new JdbcTemplate(dataSource);
}
public Collection<ForumArticle> getArticleList(){
Collection<ForumArticle> list = template.query("SELECT ARTICLE_ID, TITLE, NAME, VIEW_NUM, CREATED_DATE FROM MEMBER, FORUM WHERE MEMBER.ID = FORUM.MEMBER_ID",
new RowMapper<ForumArticle>() {
#Override
public ForumArticle mapRow(ResultSet rs, int rowNum) throws SQLException {
ForumArticle article = new ForumArticle();
System.out.println("completeeeee--------------------------------------------------------------------");
article.setArticleID(rs.getInt("ARTICLE_ID"));
article.setTitle(rs.getString("TITLE"));
article.setName(rs.getString("NAME"));
article.setViewNum(rs.getLong("VIEW_NAME"));
article.setCreatedDate(rs.getTimestamp("CREATED_DATE").toLocalDateTime());
return article;
}
});
System.out.println("-dddddddddddddddddddddddddddddddddddd " + list.size());
return list;
}
}
All the configuration set-up is done properly and I am using Oracle DB. I have another DAO class for user data and its JdbcTemplate works perfectly.
When I run my code, the list.size() returns 0 instead of 4. It does not throw any exception.
What can be the possible solution for this issue?
The following line looks wrong:
article.setViewNum(rs.getLong("VIEW_NAME"));
VIEW_NAME should be VIEW_NUM, no?
What's probably happening is that when the above line executes, the code throws a SQLException due to an unknown column in the result set, which terminates the processing and gives you an empty result.
In my Spring Batch Application, I am reading, processing and then trying to write with a ItemWriter to the database using stored procedure:
Below is what my CSV file looks like lets say which I want to read, process and write:
Cob Date;Customer Code;Identifer1;Identifier2;Price
20180123;ABC LTD;BFSTACK;1231.CZ;102.00
My ItemWriter:
#Slf4j
public class MyDBWriter implements ItemWriter<Entity> {
private final EntityDAO scpDao;
public MyWriter(EntityDAO scpDao) {
this.scpDao = scpDao;
}
#Override
public void write(List<? extends Entity> items) {
items.forEach(scpDao::insertData);
}
}
My DAO implementation:
#Repository
public class EntityDAOImpl implements EntityDAO {
#Autowired
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall simpleJdbcCall = null;
#PostConstruct
private void prepareStoredProcedure() {
simpleJdbcCall = new SimpleJdbcCall(jdbcTemplate).withProcedureName("loadPrice");
//declare params
}
#Override
public void insertData(Entity scp) {
Map<String, Object> inParams = new HashMap<>();
inParams.put("Identifier1", scp.getIdentifier1());
inParams.put("Identifier2", scp.getIdentifier1());
inParams.put("ClosingPrice", scp.getClosingPrice());
inParams.put("DownloadDate", scp.getDownloadDate());
simpleJdbcCall.execute(inParams);
}
}
My Stored procedure used to update is as follows:
ALTER PROCEDURE [dbo].[loadPrice]
#Identifier1 VARCHAR(50),
#Identifier1 VARCHAR(50),
#ClosingPrice decimal(28,4),
#DownloadDate datetime
AS
SET NOCOUNT ON;
UPDATE p
SET ClosingPrice = #ClosingPrice,
from Prices p
join Instrument s on s.SecurityID = p.SecurityID
WHERE convert(date, #DownloadDate) = convert(date, DownloadDate)
and s.Identifier1 = #Identifier1
if ##ROWCOUNT = 0
INSERT INTO dbo.Prices
(
sec.SecurityID
, ClosingPrice
, DownloadDate
)
select sec.SecurityID
, #ClosingPrice
, LEFT(CONVERT(VARCHAR, #DownloadDate, 112), 8)
from dbo.Instrument sec
WHERE sec.Identifier1 = #Identifier1
Give I have this setup, one of my requirement is that if I am unable to update/insert to the database using #Identifier1 i.e. there is no SecurityID which matched with Identifier1, I need to THEN update/insert
using the Identifier2. Second level match if you like.
How can I do this in my DAO insertData()? It is business logic and prefer in java code instead of stored proc but I am keen to look at your examples how this can be achieved.
How can I return a result of a row being updated/inserted and take decision as to whether or not to update/insert with second identifier?
For the update I would change the where clause to
WHERE convert(date, #DownloadDate) = convert(date, DownloadDate)
and (s.Identifier1 = #Identifier1 OR s.Identifier2 = #Identifier2)
and for the insert
WHERE sec.Identifier1 = #Identifier1 OR sec.Identifier2 = #Identifier2
That should work even if I haven't verified it myself. I am assuming that the given values for identifier1 and identifier2 can not match two different rows in the Instrument table.
I have a mysql table like this:
CREATE TABLE `sezione_menu` (
`id_sezione_menu` int(11) unsigned NOT NULL AUTO_INCREMENT,
`nome` varchar(256) NOT NULL DEFAULT '',
`ordine` int(11) DEFAULT NULL,
PRIMARY KEY (`id_sezione_menu`)
)ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
I use apache dbutils to query my database, with these methods:
public static List<SezioneMenu> getSezioniMenu() {
String sql = "SELECT * FROM sezione_menu";
try {
QueryRunner qr = new QueryRunner(createDataSource());
ResultSetHandler rsh = new BeanListHandler(SezioneMenu.class);
List<SezioneMenu> sezioni = (List<SezioneMenu>)qr.query(sql, rsh);
return sezioni;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
private static DataSource createDataSource() {
BasicDataSource d = new BasicDataSource();
d.setDriverClassName(DRIVER);
d.setUsername(USERNAME);
d.setPassword(PASSWORD);
d.setUrl(DB_URL);
return d;
}
Now, if i run my application, it doesn't throw exception, but some fields (not all!) of my java bean SezioneMenu are empty (integer field equals zero and string field equals empty string).
This happen also with other tables and beans.
I used this method in the past in another system configuration without problems.
You can fix it in two ways:
As per dbutils doc,
Alias the column names in the SQL so they match the Java names: select social_sec# as socialSecurityNumber from person
Subclass BeanProcessor and override the mapColumnsToProperties() method to strip out the offending characters.
If you are keeping a class like this
public class SezioneMenuBean implements Serializable {
private int idSezioneMenu;
private String nome;
private int ordine;
public SezioneMenuBean() {
}
// Getters and setters for bean values
}
As per first solution write your queries something like this SELECT id_sezione_menu AS idSezioneMenu, name, ordine FROM sezione_menu.
Or
Based on second solution you can use GenerousBeanProcessor which is a subclass of BeanProcessor it ignores underscore & case sensitivity from column name. You don't have to implement your own custom BeanProcessor
GenerousBeanProcessor is available since version 1.6 of commons-dbutils.
Usage:
// TODO initialize
QueryRunner queryRunner = null;
ResultSetHandler<List<SezioneMenuBean>> resultSetHandler =
new BeanListHandler<SezioneMenuBean>(SezioneMenuBean.class, new BasicRowProcessor(new GenerousBeanProcessor()));
// best practice is specifying only required columns in the query
// SELECT id_sezione_menu, name, ordine FROM sezione_menu
final List<SezioneMenuBean> sezioneMenuBeans = queryRunner.query("SELECT * FROM sezione_menu", resultSetHandler);
for (SezioneMenuBean sezioneMenuBean : sezioneMenuBeans) {
System.out.println(sezioneMenuBean.getIdSezioneMenu());
}
I faced the same issue of BeanHandler/BeanHandlerList returning null or 0 for database columns.
As mentioned by #aelfric5578 in the comment, I have updated the Bean class with same names as Database, DBUtils returned values correctly.
Having BeanClass defined like this will solve your problem.
public class SezioneMenuBean{
int id_sezione_menu;
String nome;
int ordine;
public SezioneMenuBean(){
}
// Getters and setters for bean values
}
I use oracle stored procedures with spring-data-jpa. In the most cases it is pretty well, when it is a function or output parameter is first in the param list. But I have some stored procedures with ouput param is the last in the param list:
procedure get_data (some_val in varchar2 cur out sys_refcursor);
or returns more than one output refcursors like
procedure get_my_data (cur1 out sys_refcursor, cur2 out sys_refcursor, some_val in varchar2);
Is it possible with any way to use it with JpaRepository?
Finally, I've found answer by myself :)
My situation is a good point for using spring-data's Custom Implementation
You should:
Create Interface, named YourRepository*Custom* with your new
method (for example EmployeeRepositoryCustom)
Create an imiplementation for this interface with name
YourRepository*Impl* (for example EmployeeRepositoryImpl)
Inside method implementation you can use
SimpleJdbcCall for calling Oracle stored procedure, for example
...
PROFIT!!!
Note: naming rules are important if you want to use default configs
well,
if you want a localized call to a StoredProc from a service or utility class, then you can use Spring Jdbc for StoredProc. See below for implementation
public class StoredProcSampleTest extends StoredProcedure {
private static final String SPROC_NAME = "HH_EXTRACT.SAMPLE_TEST";
public StoredProcSampleTest(DataSource dataSource) {
super(dataSource, SPROC_NAME);
declareParameter(new SqlParameter("v_in_msg", Types.VARCHAR));
declareParameter(new SqlOutParameter("v_out_msg", Types.VARCHAR));
compile();
}
public String execute() {
HashMap<String, Object> hmap = new HashMap<String, Object>();
hmap.put("v_in_msg", "Suresh");
hmap.put("v_out_msg", "");
Map<String, Object> results = execute(hmap);
String outRes = (String) results.get("v_out_msg");
return outRes;
}
}
now in your utility class or Service class, do this
protected StoredProcSampleTest storedProcSampleTest;
#Autowired
public void setDataSource(final DataSource dataSource) {
this.storedProcSampleTest = new StoredProcSampleTest(dataSource);
}
:
public String callStoredProcSampleTest(){
return storedProcSampleTest.execute();
}
I'm trying to do a simple thing: call stored procedure which have a object type parameter.
This is what I have in db:
create or replace
TYPE TEST_TYPE AS OBJECT
(
test_field varchar(100)
)
and
CREATE OR REPLACE PROCEDURE TEST_PROC
(
PARAM1 IN TEST_TYPE
) AS
BEGIN
END TEST_PROC;
This is what I have in my java code:
#Embeddable
#Struct(name = "TEST_TYPE", fields = {"TEST_FIELD"})
public class TestStruct
{
private String testField;
public String getTestField() {
return testField;
}
public void setTestField(String testField) {
this.testField = testField;
}
}
and
#PostConstruct
public void init()
{
StoredProcedureCall call = new StoredProcedureCall();
call.setProcedureName("TEST_PROC");
call.addNamedArgument("PARAM1", "PARAM1", Types.STRUCT, "TEST_TYPE", TestStruct.class);
DataReadQuery dataReadQuery = new DataReadQuery(call);
dataReadQuery.addArgument("PARAM1");
TestStruct testStruct = new TestStruct();
List args = new ArrayList();
args.add(testStruct);
Object result = ((EntityManagerImpl)em.getDelegate()).getSession().executeQuery(dataReadQuery,args);
}
this is what I get in runtime:
Internal Exception: java.sql.SQLException: Invalid column type
Error Code: 17004
Call: BEGIN TEST_PROC(PARAM1=>?); END;
bind => [1 parameter bound]
Query: DataReadQuery()
I think I totally don't understand the subject of usage structs with JPA
please help me, good people :)
What is the shortest way to make this working?
Please send complete your code.
For call stored procedures using Spring, you have to extends StoredProcedure class. If you send your complete code, I can help better. sample pseudo code:
class CustomStoredProcedure extends org.springframework.jdbc.object.StoredProcedure
{
CustomStoredProcedure()
{
super([your-data-source], [package-name]);
declareParameter(new SqlParameter([your-struct-name]), Types.STRUCT));
compile();
}
Map<String, Object> execute([your-parameter])
{
return super.execute(inputs);
}
}
for better help, you have explain complete situation.
Your code looks correct.
Ensure that the descriptor was defined for the struct. (i.e. session.getDescrptor(TestStruct.class))
Can you call stored procedures with other types?
What database are you using, have you set your platform correctly to Oracle?
It seems that eclipselink skips descriptors for the #Struct and #Embeddable annotated classes unless they are referenced by some other class. The shortest way to make it working is to use workaround based on this assumption. Put additional class in the jar where your META-INF/persistence.xml is located:
#Entity
public class StructEntitiesWorkaround {
#Id
private String id;
private TestStruct testStruct;
}
You might want to use SimpleJdbcCall with Types.STRUCT.
Here is an example: https://docs.spring.io/spring-data/jdbc/old-docs/2.0.0.M1/reference/html/orcl.datatypes.html