Can I define the target table for int-jdbc:outbound-channel-adapter based on the received message? - spring

I´m using Spring Integration JDBC support to persist a message in one of several tables (>20) depending on a certain condition (stored in the message headers as "table"):
<int:channel id="cmTablesJdbcChannel"></int:channel>
<int-jdbc:outbound-channel-adapter channel="cmTablesJdbcChannel"
id="cmTableJdbcOutputAdaptor" data-source="datasource"
query="insert into TABLE_NAME values (int_id, parent_int_id, name) values (:headers[int_id],:headers[parent_int_id],:headers[name])">
</int-jdbc:outbound-channel-adapter>
I have tried to replace TABLE_NAME by several expressions but none worked:
${headers['table']}
#{headers['table']}
:[headers['table]}
I´m trying to avoid the usage of 20 different outbound channel adaptors and reuse a single one but dynamically setting the name of the table to be used. Do you know if it is possible?
There were similar questions but related to the parameters to be used: How can I create a dynamic query for Spring Integration JDBC outbound-channel-adapter?

No, it doesn't work now and TABLE_NAME can't be as a parameter.
Feel free to raise JIRA issue to consider something like query-expression to build the INSERT/UPDATE SQL at runtime against request message.
In meantime you should use NamedParameterJdbcTemplate from the some custom POJO to be used from the <outbound-channel-adapter> or like a complex expression:
<service-activator input-channel="cmTablesJdbcChannel" output-channel="nullChannel"
expression="#jdbcTemplate.update('insert into ' + headers.table + ' (int_id, parent_int_id, name) values (:int_id,:parent_int_id,:name)', headers)"/>
Note, don't use direct SQL expression building with the parameter values. The parametrized variant with : is preferable way for any RDBMS. It will be compiled on the server side (indexes, query plan etc.) and reused for all other upcoming executions.

Related

Migration to SpringBoot 3: trunc a date with oracle driver no longer works (hibernate)

I am currently migrating a SpringBoot 2.7 application to SpringBoot 3. The following query is used within a SpringData repository:
#Query("select b from #{#entityName} b where (trunc(b.date) <= trunc(:date))")
List<T> findByDate(LocalDateTime date);
While this works great in SpringBoot 2.7, with SpringBoot 3 the following message is thrown:
org.hibernate.QueryException: Parameter 1 of function trunc() has type NUMERIC, but argument is of type java.time.LocalDateTime
Unfortunately, a simple migration to datetrunc was unsuccessful:
Error Msg = ORA-00904: "DATETRUNC": ungültige ID
Does anyone have a solution for this?
Best regards
So, as I noted in my previous answer, there's no standard HQL function for date truncation. That's because it would be quite difficult to implement in most SQL dialects (I have not really investigated to see quite how difficult, but it's at least nontrivial.)
However, just especially for you, in Hibernate 6.2, which should go into CR today, what I have done is added undocumented (but tested) support for the date_trunc() function under Postgres, DB2, Oracle, and H2. On Oracle, this translates to trunc(), of course.
For example, you could write:
date_trunc(year,current_timestamp)
When I say it's "undocumented" I mean you use it at your own risk. (The documented way to do this remains to use a FunctionContributor.)
I hope that helps.
UPDATE:
Oh and by the way, I just noticed that you're not actually using the full form of the trunc() function in your query. You're actually just stripping off the time part of a timestamp.
There's actually multiple ways to do that in HQL without needing to use the Oracle-specific trunc() function. (But of course trunc()/date_trunc() can do more things that you're not using here.)
JPA-standard way
One of the improvements I added to the new JPA 3.1 specification was to let you write:
extract(date from current_timestamp)
To strip off the time part of the timestamp.
HQL alternative
But you can also do it using the HQL cast() function if you prefer:
cast(current_timestamp as Date)
These two options translate to the same SQL on Oracle.
In Hibernate 6 we started checking the argument types of HQL functions.
Since trunc(numeric,places) is a very common function across many dialects of SQL, we register it as one of the known functions, though we have not yet promoted it to a “standard” HQL function (perhaps we should).
On the other hand, Oracle’s trunc(date) is extremely specific to Oracle and is more like the date_trunc() function on DB2 and Postgres. We have not (so far) attempted to standardize on any sort of function for timestamp truncation, and I don’t consider it a high priority. This function is not registered by OracleDialect.
So, therefore, if you try to call trunc() on a Date, you will get a typing error.
Now, in general, in Hibernate 6.x:
We do not guarantee that Hibernate Dialects know about every single SQL function in your database, and you can't depend on that.
Instead we have a rather long list of documented HQL functions that we promise work portably across every database we support. (Right now trunc() is not on that list, but it almost made it onto the list.)
And that's perfectly fine, because we also provide APIs that make it very easy to register new SQL functions, either platform-specific functions like this one, or functions you've written yourself, either by:
writing a custom Dialect, or
providing a FunctionContributor.
https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-user-defined-functions
Alternatively, if that's too much work, you can just use the JPA-standard syntax for calling native SQL functions, which is function('trunc', date, 'YEAR'), but I consider this a bit unlovely.
I'm using Oracle database and after migration to new hibernate I had the same issue.
I solved it by overriding org.hibernate.dialect.OracleDialect and adding ORACLE_TRUNC function to a functionRegistry. I've used the same definition trunc function definition as in Hibernate < 6 Oracle8iDialect.
public class CustomOracleDialect extends OracleDialect {
public CustomOracleDialect() {
super();
}
#Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
functionContributions.getFunctionRegistry().register("ORACLE_TRUNC", new StandardSQLFunction("trunc"));
}
}
Configure hibernate to use this dialect inapplication.properties / application.yml file:
spring:
jpa:
database-platform: com.example.CustomOracleDialect
In your queries use ORACLE_TRUNC() instead of TRUNC().
I tried using DATE_TRUNC(date, my_date) function from hibernate 6.2 RC2 as Gavin King said - but it transformed my query to CAST(my_date AS DATE) instead of TRUNC(my_date) causing date comparison issues.

DB Insert if not present - Spring Integration

We have a int-jms:message-driven-channel-adapter --> a transformer --> a filter --> another transformer --> int-jdbc:outbound-channel-adapter (to insert in table_1)
(considering --> as channels)
I want to change this flow to insert into 2 tables instead of 1 but for table_2 I want to insert only if data corresponding to some fields in the message is already not present in the table i.e. insert if not present.
One thing I figured is that I will now need a pub-sub-channel with ignore-failures=false to split the flow into 2 tables.
My question is that what component should I use to select the data to check if data exists in table_2? I first thought inbound-channel-adapter is the right choice but couldn't figure out how to slide it between 2 components i.e. say a transformer and outbound-channel-adapter.
Some things I can think of are:
1. Use a filter, passing it jdbcTemplate so that the filter itself can fire a JDBC query to accept if record doesn't exist.
2. Use a outbound-channel-adapter and the insert query should have the check for data existence also, something like insert-if. I am not sure if Oracle has something like this. I am researching.
Please point me to an example or documentation or tell me the best way.
Thanks
Actually you can use
<chain>
<header-enricher>
<header name="original" expression="payload"/>
</header-enricher>
<int-jdbc:outbound-gateway query="SELECT count(*) from TABLE where ..."/>
<filter expression="payload == 0"/>
<transformer expression="headers.original"/>
</chain>
From other side <filter> with JdbcTemplate direct usage is good choice too.
Re. insert-if. It can work too if you have a unique constraint on the table. In this case an Exception will be thrown. And if you have <publish-subscribe-channel ignore-failures=false>, it would work too.

SSIS - Join tables from different servers whose names are based on a variable

I have a simple query based on tables from two different linked servers. I need both servers to be changeable since we're moving from DEV to UAT to Production. I'm using an expression to set the Connection String and Password for server A. So, using that as a base I set a Data Flow Task and an 'OLE DB Source' to extract the data I need. Ultimately I'd like my query to look like this:
Select * from A.Payments p1
Full Outer Join ?.Payments p2 on p1.Id = p2.Id
where p1.OrderDesc is null or p2.OrderDesc is null
Is there a way around it? Can I use a variable or some kind of dynamic query? I haven't managed to parse a project parameter and run one. Thank you very much for your help.
This is done by making the Data Source SQL an expression.
Right click the Data Flow and then click the ellipsis [...] beside "Expressions". In there you will find one of the available properties you can set is the SQLCommand for your Data Flow Source.
It's not the most intuitive thing to be fair.

Nhibernate Update timestamp

Is there a way to do
"UPDATE Item SET start_date = CURRENT_TIMESTAMP" ?
in Nhibernate without using hql/sql.
I am trying to avoid hql/sql because the rest of my code is in criteria. I want to do something like :
var item = session.get<Item>(id)
item.start_date = current_timestamp
There are two ways and sql is correct one.
Either you will
load all entities, change, update and commit, or
write sql query and let dbms handle most of the work
I am trying to avoid hql/sql because the rest of my code is in criteria
That is not a valid argument. Criteria is an API intended for relational search, and it does not support mass updates.
Different tasks, different APIs.
In this case, you can use either HQL or SQL, as the syntax is the same. I recommend the former, because you'll be using your entity/property names instead of table/column ones.

Cache and SqlCacheDependency (ASP.NET MVC)

We need to return subset of records and for that we use the following command:
using (SqlCommand command = new SqlCommand(
"SELECT ID, Name, Flag, IsDefault FROM (SELECT ROW_NUMBER() OVER (ORDER BY #OrderBy DESC) as Row, ID, Name, Flag, IsDefault FROM dbo.Languages) results WHERE Row BETWEEN ((#Page - 1) * #ItemsPerPage + 1) AND (#Page * #ItemsPerPage)",
connection))
I set a SqlCacheDependency declared like this:
SqlCacheDependency cacheDependency = new SqlCacheDependency(command);
But immediately after I run the command.ExecuteReader() instruction, the hasChanged base property of the SqlCacheDependency object becomes true although I did not change the result of the query in any way! And, because of this, the result of this query is not kept in cache.
HttpRuntime.Cache.Insert( cacheKey, list, cacheDependency, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(AppConfiguration.CacheExpiration.VeryLowActivity));
Is it because the command has 2 SELECT statements? Is it ROW_NUMBER()? If yes, is there any other way to paginate results?
Please help! After too many hours, a little will be greatly appreciated! Thank you
Running into the same issue and finding the same answers online without any help, I was reasearching the xml invalid subsicription response from profiler.
I found an example on msdn support site that had a slightly different order of code. When I tried it I realized the problem - Don't open your connection object until after you've created the command object and the cache dependency object. Here is the order you must follow and all will be good:
Be sure to enable notifications (SqlCahceDependencyAdmin) and run SqlDependency.Start first
Create the connection object
Create the command object and assign command text, type, and connection object (any combination of constructors, setting properties, or using CreateCommand).
Create the sql cache dependency object
Open the connection object
Execute the query
Add item to cache using dependency.
If you follow this order, and follow all other requirements on your select statement, don't have any permissions issues, this will work!
I believe the issue has to do with how the .NET framework manages the connection, specifically what settings are set. I tried overriding this in my sql command test but it never worked. This is only a guess - what I do know is changing the order immediately solved the issue.
I was able to piece it together from the following to msdn posts.
This post was one of the more common causes of the invalid subscription, and shows how the .Net client sets the properties that are in contrast to what notification requires.
https://social.msdn.microsoft.com/Forums/en-US/cf3853f3-0ea1-41b9-987e-9922e5766066/changing-default-set-options-forced-by-net?forum=adodotnetdataproviders
Then this post was from a user who, like me, had reduced his code to the simplest format. My original code pattern was similar to his.
https://social.technet.microsoft.com/Forums/windows/en-US/5a29d49b-8c2c-4fe8-b8de-d632a3f60f68/subscriptions-always-invalid-usual-suspects-checked-no-joy?forum=sqlservicebroker
Then I found this post, also a very simple reduction of the problem, only his was a simple issue - needing 2 part name for tables. In his case the suggestion resolved the issue. After looking at his code I noticed the main difference was waiting to open the connection object until AFTER the command object AND the dependency object were created. My only assumption is under the hood (I have not yet started reflector to check so only an assumption) the Connection object is opened differently, or order of events and command happen differently, because of this association.
https://social.msdn.microsoft.com/Forums/sqlserver/en-US/bc9ca094-a989-4403-82c6-7f608ed462ce/sql-server-not-creating-subscription-for-simple-select-query-when-using-sqlcachedependency?forum=sqlservicebroker
I hope this helps someone else in a similar issue.
Just a guess, but could it be because your SELECT statement doesn't have an ORDER BY clause?
If you don't specify an explicit ordering then it's possible for the query to return the results in any order each time it is run. Maybe this is causing the SqlCacheDependency object to think that the results have changed.
Try adding an ORDER BY clause:
SELECT ID, Name, Flag, IsDefault
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY #OrderBy DESC) AS Row,
ID, Name, Flag, IsDefault
FROM dbo.Languages
) AS results
WHERE Row BETWEEN ((#Page - 1) * #ItemsPerPage + 1) AND (#Page * #ItemsPerPage)
ORDER BY Row
i'm no expert on SqlCacheDependency, in fact, i found this question whilst looking for answers to my own issues with it! However, i believe the reason your SqlCacheDependency is not working is because your SQL contains a nested sub query.
Take a look at the documentation which lists what you can/can not use in your SQL: Creating a Query for Notification
"....The statement must not contain subqueries, outer joins, or self-joins....."
I also found some invaluable troubleshooting info from a guy at Redgate here: Using and Monitoring SQL 2005 Query Notification that helped me solve my own problem: By using Sql Profiler to trace the QN events he suggests, i was able to spot my connection was incorrectly using the 'SET ARITHABORT OFF' option, causing my notifications to fail.

Resources