Unable to Read 2nd Refcursor in a Spring Boot application with Hibernate and Oracle 11g Database - oracle

Problem Details
We are working on a Spring Boot application where in we are connecting to Stored Procs (all returning multiple refcursors) using #NamedStoredProcedureQuery for Oracle 11g DB. We are unable to read data from the second cursor at the same time. As soon as we provide the second result class for the 2nd refcursor we are getting an exception titled Invalid Column Name. Reading 1 cursor works fine.
Exception Details
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException:
Error extracting results from CallableStatement Caused by:
java.sql.SQLException: Invalid column name
Implementation Details
STORED PROCEDURE QUERY
#NamedStoredProcedureQuery (
name = "getSP1Data",
procedureName = "package_name",
resultClasses = {Cursor1Response.class, Cursor2Response.class},
parameters = {
#StoredProcedureParameter(type = Integer.class, mode = ParameterMode.IN, name = "in_param_1"),
#StoredProcedureParameter(type = void.class, mode = ParameterMode.REF_CURSOR, name = "cursor_1"),
#StoredProcedureParameter(type = void.class, mode = ParameterMode.REF_CURSOR, name = "curosr_2"),
#StoredProcedureParameter(type = String.class, mode = ParameterMode.INOUT, name = "in_out_param_2") }
)
CURSOR RESPONSE CLASSES
#Entity
public class Cursor1Response {
#Id
#Column(name = "column_name_1")
private Date column1;
#Column(name = "column_name_2")
private Double column2;
}
#Entity
public class Cursor2Response {
#Id
#Column(name = "column_name_1")
private Date column1;
#Column(name = "column_name_2")
private Double column2;
}
DAO LAYER IMPLEMENTATION (FROM WHERE WE ACTUALLY CALL OUR STORED PROC QUERY)
StoredProcedureQuery query = entityManager.createNamedStoredProcedureQuery("getSP1Data");
// Code for setting all in params
query.getResultList(); // While execution of this line it is
throwing the above mentioned exception
Has anyone worked on such scenario and have any ideas how to fix this exception?

I had the same issue. First I tried downgrading the Oracle JDBC driver from 8 to 6 with no results. After that I changed the annotation with a more programmatic stored procedure definition (note: avoid positional parameters, they cause the same problem...):
EntityManager entityManager = Persistence.createEntityManagerFactory("namehere").createEntityManager();
StoredProcedureQuery spq =
entityManager.createStoredProcedureQuery("YOUR.SP.NAME")
.registerStoredProcedureParameter("name1", String.class, ParameterMode.IN)
.registerStoredProcedureParameter("name2", Integer.class, ParameterMode.OUT)
.registerStoredProcedureParameter("name3", String.class, ParameterMode.OUT)
.registerStoredProcedureParameter("name4", void.class,ParameterMode.REF_CURSOR)
.setParameter(SP_PARAM_MSISDN, "paramval");
spq.execute();

Related

wrong number or types of arguments while calling Stored Proc

I am calling Stored Proc from Spring Data JPA :
Procedure is:
create or replace procedure GET_LATEST_GC (arg1 IN VARCHAR2, res1 OUT VARCHAR2, res2 OUT VARCHAR2)
AS
BEGIN
DELETE FROM GC_T WHERE id = arg1;
COMMIT;
BEGIN
SELECT gc.NAME, s.SIP INTO res1, res2
FROM GC_T gc, STAFF_T s WHERE s.id = gc.id
AND START_TIME = (SELECT MAX(START_TIME) FROM GC_T);
EXCEPTION
WHEN others THEN
res1 := '';
END;
END;
Spring Data JPA code
//Repository
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1);
}
//Entity
#Data
#Entity
#NamedStoredProcedureQuery(name = "GET_LATEST_GC",
procedureName = "GET_LATEST_GC", parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "arg1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res1", type = String.class),
#StoredProcedureParameter(mode = ParameterMode.OUT, name = "res2", type = String.class)})
#Table(schema = "abc", name = "GC_T")
public class GcT implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "NAME")
private String name;
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg);
Procedure is accepting one parameter and I am also passing 1 argument.Then also I am getting this error:
Hibernate: {call GET_LATEST_GC(?,?)}
ERROR o.h.e.jdbc.spi.SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column 7:\nPL/SQL: Statement ignored\n
Please let me know where I am doing wrong.
Thank you
Update- Tried to add OUT params also as per suggestion
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(procedureName="GET_LATEST_GC")
Object[] plus1(#Param("arg1") String arg1,#Param("res1") String res1,#Param("res2") String res2);
}
//Call
Object[] activeGCInfo =activeDao.plus1(arg,"","");
I am sending three args but it is showing me 4 args in error:
Hibernate: {call GET_LATEST_GC(?,?,?,?)} SqlExceptionHelper - ORA-06550: line 1, column 7:\nPLS-00306: wrong number or types of
arguments in call to 'GET_LATEST_GC'\nORA-06550: line 1, column
7:\nPL/SQL: Statement ignored\n
Try changing the result from Object[] to Map<String, Object, along with referencing the proc name with name instead of procedureName. Based on the error, I'm not sure that it will fix it. Spring Data JPA does expect a Map as the return value for multiple output params, so each output param can be found as the key in that Map. But I think the main error is that procedureName maps directly to the db, but name= will map to the correct Entity
//Repo
public interface ActiveDao extends JpaRepository<GcT,Integer> {
#Procedure(name="GET_LATEST_GC")
Map<String, Object> plus1(#Param("arg1") String arg1);
}
//Call
Map<String, Object> activeGCInfo =activeDao.plus1(arg);
Here's what happened:
you declared a procedure with 3 parameters: 1 in and 2 out
you said: "Procedure is accepting one parameter and I am also passing 1 argument"
that was the 1st procedure's parameter (arg1 IN)
it results in "PLS-00306: wrong number or types of arguments"
Of course it does; you need to provide 2 more arguments (datatype should be able to accept VARCHAR2 values returned by the procedure).

Spring Data JPA + Oracle Trigger increments the ID twice

I use the following tech stack:
spring-boot-starter-data-jpa
HikariCP for connection pooling
Oracle DB
My actual code looks similar to this.
/// My trigger looks like this
CREATE OR REPLACE TRIGGER FILE_BRI
BEFORE INSERT
ON FILE
FOR EACH ROW
BEGIN
SELECT FILE_SEQ.NEXTVAL INTO :NEW.ID FROM DUAL;
END;
///
public class FILE implements Serializable {
#Id
#SequenceGenerator(
name = "FILE_SEQ",
sequenceName = "FILE_SEQ",
allocationSize = 1)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "FILE_SEQ"
)
private long id;
}
public class ServiceA () {
#Transactional(propagation = REQUIRES_NEW, isolation = READ_COMMITTED)
public File insertFile() {
// Below line returns the inserted File object with ID as '58496'
return fileRepository.save(file)
}
#Transactional(propagation = REQUIRES_NEW, isolation = READ_COMMITTED)
public AccessControl insertAccessControl() {
// Below line results in 'SQLIntegrityConstraintViolationException' (full error at the bottom of this post)
return accessControlRepository.save(accessControlFile)
}
}
Public class FileProcessor() {
ServiceA serviceA;
public void someMethod() {
// insert the file and get the inserted record
File insertedFile = serviceA.insertFile(file);
// get the ID from the inserted file and make another insert into another table
serviceA.insertAccessControl(insertedFile.getId()); // inserted file ID is '58496'
}
}
This is my investigation:
When I verified the ID of the inserted record in the table "FILE" is '58497', however repository.save() returned a different value.
When I make the second insert on table "ACCESS_CONTROL_FILE" with FILE_ID as '58496' it results in the error below because the FILE with ID as '58496' does not exist.
Caused by: java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL into ("DB_OWNER"."ACCESS_CONTROL_FILE"."FILE_ID")
I'm puzzled as to why would repository.save() return a different ID(i.e. ID=58496) than what is actually inserted(ID=58497) in the database!
I've investigated all options that I could find on the internet related to 'Propagation and Isolation'.
As mentioned in comments, Looks like a database trigger is causing the issue. Disable the trigger to let JPA to manage the ID generation.

Native SQL from Spring / Hibernate without entity mapping?

I need to write some temporary code in my existing Spring Boot 1.2.5 application that will do some complex SQL queries. By complex, I mean a single queries about 4 different tables and I have a number of these. We all decided to use existing SQL to reduce potential risk of getting the new queries wrong, which in this case is a good way to go.
My application uses JPA / Hibernate and maps some entities to tables. From my research it seems like I would have to do a lot of entity mapping.
I tried writing a class that would just get the Hibernate session object and execute a native query but when it tried to configure the session factory it threw an exception complaining it could not find the config file.
Could I perhaps do this from one of my existing entities, or at least find a way to get the Hibernate session that already exists?
UPDATE:
Here is the exception, which makes perfect sense since there is no config file to find. Its app configured in the properties file.
org.hibernate.HibernateException: /hibernate.cfg.xml not found
at org.hibernate.internal.util.ConfigHelper.getResourceAsStream(ConfigHelper.java:173)
For what it's worth, the code:
#NamedNativeQuery(name = "verifyEa", query = "select account_nm from per_person where account_nm = :accountName")
public class VerifyEaResult
{
private SessionFactory sessionFact = null;
String accountName;
private void initSessionFactory()
{
Configuration config = new Configuration().configure();
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(config.getProperties()).getBootstrapServiceRegistry();
sessionFact = config.buildSessionFactory(serviceRegistry);
}
public String getAccountName()
{
// Quick simple test query
String sql = "SELECT * FROM PER_ACCOUNT WHERE ACCOUNT_NM = 'lynnedelete'";
initSessionFactory();
Session session = sessionFact.getCurrentSession();
SQLQuery q = session.createSQLQuery(sql);
List<Object> result = q.list();
return accountName;
}
}
You can use Data access with JDBC, for example:
public class Client {
private final JdbcTemplate jdbcTemplate;
// Quick simple test query
final static String SQL = "SELECT * FROM PER_ACCOUNT WHERE ACCOUNT_NM = ?";
#Autowired
public Client(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<Map<String, Object>> getData(String name) {
return jdbcTemplate.queryForList(SQL, name);
}
}
The short way is:
jdbcTemplate.queryForList("SELECT 1", Collections.emptyMap());

Using function in where clause with clob parameter

We are using a #NamedNativeQuery to fetch entities from our database that are qualified by the stored procedure flexmatch in the where clause of a query.
This works fine in general, but when the parameter chimeString exceeds 4.000 characters it fails raising the following exception:
ORA-01460: unimplemented or unreasonable conversion requested
This does make sense, as 4.000 characters are Oracle's border between String and Clob.
We tried to
use org.hibernate.engine.jdbc.ClobProxy
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter("chime", ClobProxy.generateProxy(chimeString))
.getResultList();
use javax.persistence.criteria.ParameterExpression together with org.hibernate.engine.jdbc.ClobProxy
ParameterExpression<Clob> chimeParam = entityManager
.getCriteriaBuilder()
.parameter(Clob.class, "chime");
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter(chimeParam, ClobProxy.generateProxy(chimeString))
.getResultList();
Libraries & System:
Oracle 11g
Hibernate 3.6.6
The find method.
public List<Structure> findByExactMatch(String chimeString) {
return entityManager
.createNamedQuery("Structure.findByExactMatch", Structure.class)
.setParameter("chime", chimeString)
.getResultList();
}
The Structure entity.
#Entity
#NamedNativeQueries({
#NamedNativeQuery(
name = "Structure.findByExactMatch",
query = "SELECT id, molfile(ctab) ctab FROM structure " +
"WHERE flexmatch(ctab, :chime, 'all')=1",
resultClass = Structure.class) })
public class Structure {
#Id
#Column(name = "ID")
private long id;
#Lob
#Column(name = "CTAB")
private String ctab;
// getter & setter
}
Edit 1 The pl/sql function, as you can see it is overloaded.
FUNCTION flexmatch(
molobj IN BLOB,
querymol IN VARCHAR2,
args IN VARCHAR2)
RETURN NUMBER
FUNCTION Flexmatch(
molobj IN BLOB,
querymol IN CLOB,
args IN VARCHAR2)
RETURN NUMBER
After some days of trying, we gave up to solve it within Hiberante. We ran the query using SpringJDBC, which is also present in the project, and used the ID to populate a Hiberante entity. You could do this with plain old JDBC also.

JPA CriteriaBuilder: Predicate with a Serializable field throws an exception

I have an entity with a persistent field declared as Serializable.
I would like to build a query with the CriteriaBuilder, that filters the results by the Serializable field.
The database is Oracle, and the field type is RAW(255) as hbm2ddl defined it.
If i write the query with a plain JPQL TypedQuery, everything works fine (the Serializable field is the one with the name "entityId"):
TypedQuery<Change> query = em.createQuery("FROM Change c WHERE c.entityClass = :class AND c.entityId = :id", Change.class);
query.setParameter("class", Person.class.getName());
query.setParameter("id", new Integer(2287));
query.getResultList();
However, the very same query with criteria builder does not work:
final CriteriaBuilder builder = em.getCriteriaBuilder();
final CriteriaQuery<Change> criteriaQuery = builder.createQuery(Change.class);
final Root<Change> from = criteriaQuery.from(Change.class);
final CriteriaQuery<Change> select = criteriaQuery.select(from);
final List<Predicate> predicates = new ArrayList<>();
predicates.add(builder.equal(from.get("entityClass"), Person.class.getName()));
predicates.add(builder.equal(from.get("entityId"), new Integer(2287)));
select.where(predicates.toArray(new Predicate[predicates.size()]));
final TypedQuery<Change> query = em.createQuery(select);
query.getResultList();
It throws the following exception after invoking getResultList():
[2013-05-21 16:12:45,960] [com.mycompany.myproduct.server.Main.startServer(Main.java:56)] [ERROR] [main] - Error starting Server: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1387)
...
Caused by: org.hibernate.exception.SQLGrammarException: could not extract ResultSet
...
Caused by: java.sql.SQLSyntaxErrorException: ORA-00932: inconsistent datatypes: expected BINARY got NUMBER
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:445)
...
Change.java:
#Entity
#Table(name = "T_REVISION_CHANGE")
#SequenceGenerator(name = "seq_revision_change", sequenceName = "SEQ_REVISION_CHANGE", allocationSize = 1)
public class Change {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_revision_change")
private Integer id;
#Column(name = "ENTITY_CLASS")
private String entityClass;
#Column(name = "ENTITY_ID")
private Serializable entityId;
}
I tried to manually serialize the Integer but the same kind of exception was thrown saying that a Serializable instance was expected instead of a byte array... :)
Any comment would be much appreciated.

Resources