Hibernate StoredProcedure with joins and Temporary table - java

I have a MySQL StoredProcedure(EmployeeAbsentReport Procedure)for Given Two Dateranges. which is Successfully running on MySql Commandprompt.
How can I run this Stored Procedure using Hibernate,I found one Example on How to call Stored Procedure Example,But in the Example program
he worked with a single table.
Procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS `AbsentReportproc`$$
CREATE DEFINER=`root`#`localhost` PROCEDURE `AbsentReportproc`(IN _fromdate DATETIME, IN _todate DATETIME)
BEGIN
CREATE TEMPORARY TABLE daterange25 (dte DATE);
SET #counter := 0;
WHILE (#counter < DATEDIFF(DATE(_todate), DATE(_fromdate))) DO
INSERT INTO daterange25 VALUES (DATE_ADD(_fromdate, INTERVAL #counter:=#counter + 1 DAY));
END WHILE;
SELECT tp.EMPCODE,tp.NAME,tp.DEPARTMENT, Group_Concat(d.dte order by d.dte SEPARATOR '\n')AbsentDate, COUNT(tp.EMPCODE) Totalnoofabsentdates
FROM Master tp
JOIN daterange25 d
LEFT JOIN Transactions tpt ON (tp.EMPCODE = tpt.empcode) AND DATE(S_DateTime) = d.dte
WHERE tpt.empcode IS NULL
GROUP BY tp.EMPCODE;
DROP TABLE daterange25;
END$$
DELIMITER ;
how to call a Stored Procedure using Hibernate with joins(Including a Temporary Table)and writing sql-query in XML mapping file?

Following the common framework method which i have used in one of my project to call the Stored Procedure of MySQL database :
#Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public <T> List<T> executeSP(final String spName,
final String[] parameterNames, final String[] parameterValues,
final String[] resultColumnNames, final int offset, final int size,
final Class<T> returnType) {
validateParams(parameterNames, parameterValues);
return (List<T>) hibernateTemplate
.execute(new HibernateCallback<List<T>>() {
#SuppressWarnings("unchecked")
public List<T> doInHibernate(final Session session) {
SQLQuery query = session.createSQLQuery("call "
+ spName);
if (returnType != null) {
query.addEntity(returnType);
}
setQueryParams(parameterNames, parameterValues, query);
setResultColumnNames(resultColumnNames, query);
if (offset >= 0) {
query.setFirstResult(offset);
}
if (size > 0) {
query.setMaxResults(size);
}
return query.list();
}
});
}
Next, i would suggest that please make the Temporary Table join into the Stored Procedure only, instead of make it join with SP, if execute in SP then it is better from performance point of view also.

Related

Execute multiple queries with parameters on a single connection using jdbc template

I need to execute multiple queries via jdbc template in a single connection.
I am using SQL server with the mssql jdbc driver. Here is the query:
IF OBJECT_ID('tempdb..#temp') IS NULL
SELECT * INTO #temp FROM ( SELECT * FROM "complex query here" ) a
DECLARE #page_size INT
SET #page_size = ?
SELECT *
FROM ( SELECT TOP(#page_size) * FROM #temp
WHERE custom_column > ?
ORDER BY custom_column ) inner_query
(MULTIPLE JOINES HERE)
ORDER BY custom_column
The query simply puts the whole result from the complex query to the temp table if the table does not exist and selects pages from the generated temp table.
Since the temp tables have connection scope, i need to execute the whole paging with multiple queries in a single connection. This is the code i have tried:
try (Connection connection = dataSource.getConnection()) {
while (true) {
List<CustomObject> customObjects = customRepository.
.getCustomObjectsPage(connection, pageSize, minimumValueInWhere);
// business logic here..
if (customObjects.size() < pageSize) {
break;
}
minimumValueInWhere = customObjects.get(customObjects.size() - 1).getId().toString();
}
}
and getCustomObjectsPage():
public List<CustomObject> getCustomObjectsPage(Connection connection, int pageSize,
String parameter) {
JdbcTemplate singleConnectionJdbcTemplate = new JdbcTemplate(
new SingleConnectionDataSource(connection, true));
try {
return singleConnectionJdbcTemplate
.query("the query from above", new Object[]{pageSize, parameter},
JdbcTemplateMapperFactory.newInstance()
.newResultSetExtractor(CustomObject.class)); // the root entity.
}
}
But for some reasons the queries are not executed in a single connection, and if i hardcode the
parameters to the query instead of passing them to the jdbc template it works perfectly.
How to make this work independently of the parameters ?

Spring JDBC Template - Oracle DB Function returns null

I have my Oracle DB Stored Function as below:
CREATE OR REPLACE FUNCTION FN_EMP_CNT (EMP_ID NUMBER) RETURN NUMBER
IS
OLD_COUNT NUMBER(5) DEFAULT 0;
NEW_COUNT NUMBER(5) DEFAULT 0;
BEGIN
SELECT
COUNT(EMP_ID) INTO OLD_COUNT
FROM
OLD_DEPT
WHERE
EID = EMP_ID
AND DEPT_STAT='Closed';
SELECT
COUNT(EMP_ID) INTO NEW_COUNT
FROM
NEW_DEPT
WHERE
EID = EMP_ID
AND DEPT_STAT='Closed'
RETURN (NEW_COUNT + OLD_COUNT);
END;
When I use the below sql query directly it returns the correct number as 2:
SELECT FN_EMP_CNT(123) FROM DUAL;
But when I use Spring JDBC Template for retrieving the data it returns null.
int noOfEmps = jdbcTemplate.queryForObject("SELECT FN_EMP_CNT(?) FROM DUAL", new Object[] { empID}, Integer.class);
The most probable cause is that you use a wrong order of parameters, see Javadoc queryForObject
queryForObject(java.lang.String sql, java.lang.Class<T> requiredType, java.lang.Object... args)
Query given SQL to create a prepared statement from SQL and a list of
arguments to bind to the query, expecting a result object.
So use first the required return type followed by the parameter
This works for my fine
sql = 'SELECT FN_EMP_CNT(?) FROM DUAL'
res = jdbcTemplate.queryForObject(sql, Integer.class, 4)
HTH

select scope_identity() using createSQLQuery in Hibernate

I am forced to use createSQLQuery to insert values into tables with an Identity column (the first column and the primary key) using hibernate. Using hibernate classes are not an option since the tables are created on the fly for each customer that is added to the system. I have run the query and it successfully inserts into the table. I then execute a "select scope_identity()" and it always returns null. "select ##Identity" works but that is not guaranteed to be the correct one. I have also tried to append "select scope_identity()" to the insert query. Then I tried query.list() and query.uniqueResult() both of which throw the hibernate exception of "No Results ..."
Session session = DatabaseEngine.getSessionFactory().openSession();
String queryString = "insert into table1 (dataid) values (1)"
SQLQuery query = session.createSQLQuery(insertQueryString);
query.executeUpdate();
query = session.createSQLQuery("select scope_identity()");
BigDecimal entryID = (BigDecimal)query.uniqueResult();
The simple example table is defined as follows:
"CREATE TABLE table1 (EntryID int identity(1,1) NOT NULL," +
"DataID int default 0 NOT NULL, " +
"PRIMARY KEY (EntryID))";
Is there a way I am missing to use scope_identity() with createSQLQuery?
Actually the SQLServerDialect class used by Hibernate uses the same "scope_identity()" too.
The reason why it's not working is because you need to execute those in the same statement or stored procedure.
If you execute the scope_identity() call in a separate statement, SQL Server will not be able to give you last inserted identity value.
You cannot do it with the SQLQuery, even Hibernate uses JDBC to accomplish this task. I wrote a test on GitHub to emulate this and it works like this:
Session session = entityManager.unwrap(Session.class);
final AtomicLong resultHolder = new AtomicLong();
session.doWork(connection -> {
try(PreparedStatement statement = connection.prepareStatement("INSERT INTO post VALUES (?) select scope_identity() ") ) {
statement.setString(1, "abc");
if ( !statement.execute() ) {
while ( !statement.getMoreResults() && statement.getUpdateCount() != -1 ) {
// do nothing until we hit the resultset
}
}
try (ResultSet rs = statement.getResultSet()) {
if(rs.next()) {
resultHolder.set(rs.getLong(1));
}
}
}
});
assertNotNull(resultHolder.get());
The code uses Java 8 lambdas instead of anonymous classes, but you can easily port it to Java 1.7 too.

Hibernate is 1000 times slower than sql query

I have this setup
#Table(name ="A")
EntityA {
Long ID;
List<EntityB> children;
}
#Table(name ="B")
EntityB {
Long ID;
EntityA parent;
EntityC grandchild;
}
#Table(name ="C")
EntityC {
Long ID;
}
The SQL query is this (I omitted irrelevant details):
select top 300 from A where ... and ID in (select parent from B where ... and grandchild in (select ID from C where ...)) order by ...
The sql query in direct database or through Hibernate (3.5) SQL runs 1000 faster than using Criteria or HQL to express this.
The SQL generated is identical from HQL and Criteria and the SQL I posted there.
[EDIT]: Correction - the sql was not identical. I didn't try the Hibernate style parameter setting on the management studio side because I did not realize this until later - see my answer.
If I separate out the subqueries into separate queries, then it is fast again.
I tried
removing all mappings of child, parent, ect.. and just use Long Id references - same thing, so its not a fetching, lazy,eager related.
using joins instead of subqueries, and got the same slow behaviour with all combinations of fetching and loading.
setting a projection on ID instead of retrieving entities, so there is no object conversion - still slow
I looked at Hibernate code and it is doing something astounding. It has a loop through all 300 results that end up hitting the database.
private List doQuery(
final SessionImplementor session,
final QueryParameters queryParameters,
final boolean returnProxies) throws SQLException, HibernateException {
final RowSelection selection = queryParameters.getRowSelection();
final int maxRows = hasMaxRows( selection ) ?
selection.getMaxRows().intValue() :
Integer.MAX_VALUE;
final int entitySpan = getEntityPersisters().length;
final ArrayList hydratedObjects = entitySpan == 0 ? null : new ArrayList( entitySpan * 10 );
final PreparedStatement st = prepareQueryStatement( queryParameters, false, session );
final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
// would be great to move all this below here into another method that could also be used
// from the new scrolling stuff.
//
// Would need to change the way the max-row stuff is handled (i.e. behind an interface) so
// that I could do the control breaking at the means to know when to stop
final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() );
final boolean createSubselects = isSubselectLoadingEnabled();
final List subselectResultKeys = createSubselects ? new ArrayList() : null;
final List results = new ArrayList();
try {
handleEmptyCollections( queryParameters.getCollectionKeys(), rs, session );
EntityKey[] keys = new EntityKey[entitySpan]; //we can reuse it for each row
if ( log.isTraceEnabled() ) log.trace( "processing result set" );
int count;
for ( count = 0; count < maxRows && rs.next(); count++ ) {
if ( log.isTraceEnabled() ) log.debug("result set row: " + count);
Object result = getRowFromResultSet(
rs,
session,
queryParameters,
lockModesArray,
optionalObjectKey,
hydratedObjects,
keys,
returnProxies
);
results.add( result );
if ( createSubselects ) {
subselectResultKeys.add(keys);
keys = new EntityKey[entitySpan]; //can't reuse in this case
}
}
if ( log.isTraceEnabled() ) {
log.trace( "done processing result set (" + count + " rows)" );
}
}
finally {
session.getBatcher().closeQueryStatement( st, rs );
}
initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) );
if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session );
return results; //getResultList(results);
}
In this code
final ResultSet rs = getResultSet( st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), selection, session );
it hits the database with the full SQL, but there are no results collected anywhere.
Then it proceeds to go through this loop
for ( count = 0; count < maxRows && rs.next(); count++ ) {
Where for every one of the expected 300 results, it ends up hitting the database to get the actual result.
This seems insane, since it should already have all the results after 1 query. Hibernate logs do not show any additional SQL being issued during all that time.
Anyone have any insight? The only option I have is to go to native SQL query through Hibernate.
I finally managed to get to the bottom of this. The problem was being caused by Hibernate setting the parameters separately from the actual SQL query that involved subqueries. So native SQL or not, the performance will be slow if this is done. For example this will be slow:
String sql = some sql that has named parameter = :value
SQLQuery sqlQuery = session.createSQLQuery(sql);
sqlQuery.setParameter ("value", someValue);
List<Object[]> list = (List<Object[]>)sqlQuery.list();
And this will be fast
String sql = some native sql where parameter = 'actualValue'
SQLQuery sqlQuery = session.createSQLQuery(sql);
List<Object[]> list = (List<Object[]>)sqlQuery.list();
It seems that for some reason with letting Hibernate take care of the parameters it ends up getting stuck in the resultSet fetching. This is probably because the underlying query on the database is taking much longer being parameterized. I ended up writing the equivalent of Hibernate Criteria and Restrictions code that sets the parameters directly as above.
We noticed a similar behaviour in our system.
And also encountered that writing the query with hardcoded parameters instead of using setParameter() would fixed the issue.
We are using MS SQL Server and after further investigation we noticed the the root cause of our issue is a default configuration of the sql server driver that transmits the query parameters as unicode. This lead to our indices being ignored since they were based on the ascii values on the queried columns.
The solution was to setup this property in the jdbc url : sendStringParametersAsUnicode=false
More details can be found here.
https://stackoverflow.com/a/32867579

"super-dynamic" query with MyBatis

Is there way to create sql query on the fly with MyBatis? To concretize: I have a query, where part of it (but not parameter) needs to be created in the runtime:
with dummy (id) as (
values (#{rangeEnd}) union all
select id - 1 from dummy where id - 1 >= #{rangeStart}
).......
The second part can be used as parameter, but, when trying the query as it is I get an exception:
[SQL0584] NULL or parameter marker in VALUES not allowed.
With plain JDBC I use MessageFormat:
PreparedStatement ps = connection.prepareStatement(
MessageFormat.format(MY_QUERY, currentRange.getRangeEnd()))
, but I haven't found a way how to do it with MyBatis.
It's really easy (answer from Dynamic Select SQL statements with MyBatis):
with dummy (id) as (
values (${rangeEnd}) union all
select id - 1 from dummy where id - 1 >= #{rangeStart}
).......
Use #SelectProvider annotation:
public interface SqlMapper {
static class PureSqlProvider {
public String sql(String sql) {
// Create your query here
return sql;
}
}
#SelectProvider(type = PureSqlProvider.class, method = "sql")
public List<Dummy> select(String sql);
}

Categories