How to write a custom where clause in JPA orm.xml file - spring-boot

My Springboot application has orm.xml file in my WEB-INF folder.
I have few named native queries in the orm.xml file.
I want to add a where condition in the query based on the values in the parameter to the query.
I am looking for some thing like this:
<named-native-query name="MyReport.countInventory">
<query>
SELECT COUNT(*) AS COUNT
FROM
Inventory
WHERE
<if :date>
add_date = :date
</if>
</query>
</named-native-query>

This is not possible with JPA.
It just allows straight-forward SQL statements without any conditional processing.
In many cases, one can actually implement the required conditional logic in SQL itself as Turo described in his/her comment.
Since you are seeming to be using Spring Data JPA you can also use Specifications to dynamically create conditions.
Another alternative would be to use MyBatis which allows constructing SQL statements in a way similar to what you outline in your question.
The following example is taken from http://www.mybatis.org/mybatis-3/dynamic-sql.html
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>

Related

Filtering query with R2DBC

I need do a filtering query with some query parameter. I need that if the query parameter is null, the condition of the query (= or LIKE, for example) is not evaluated and return me everything. I'm using R2DBC and I don't find a way to solve it.
If you are using Spring Data R2dbc, besides the above raw SQL query, you can use R2dbcOperations to compose Criteria by condition freely.
The following is an example.
template
.select(Post.class)
.matching(
Query
.query(where("title").like("%" + name + "%"))// extract where here you can assemble the query condition freely.
.limit(10)
.offset(0)
)
.all();
Additionally using R2dbcRepository and convention-based method, try to use a default value in the like(eg. set the null to empty string "" in like) to save work to determine if it is a null value in sql.
A general prepared statement which would work might be:
SELECT *
FROM yourTable
WHERE col = ? or ? IS NULL;
In the event that you bind a NULL value to the ? from your obfuscation layer, the WHERE clause would always be true, returning all records in the table.
If you prefer doing this with a "static SQL statement" (meaning you use a single SQL string for all possible bind values), then, in Oracle, it's probably optimal to use NVL() to profit from an Oracle optimiser feature, as explained in this article, irrespective of whether you're using R2DBC:
SELECT *
FROM t
WHERE col = nvl(:bind, col)
However, a query like yours is often best implemented using dynamic SQL, such as supported by a third party library like jOOQ:
Flux<TRecord> result =
Flux.from(ctx
.selectFrom(T)
.where(bind == null ? noCondition() : T.COL.eq(bind))
);
You can obviously also do this yourself, directly with R2DBC and your own dynamic SQL library, or any other such library.
Disclaimer: I work for the company behind jOOQ.

mybatis nested select vs. nested results

I'm a junior user of mybatis, I wonder the difference of nested select and nested results whether it's just simply like the difference between sub-query vs. join, especially in performance. Or it will do some optimization?
I used mybatis 3.4.7 version and oracle DB.
here is an example for reference:
private List<Post> posts;
<resultMap id="blogResult" type="Blog">
<collection property="posts" javaType="ArrayList" column="id"
ofType="Post" select="selectPostsForBlog"/>
</resultMap>
<select id="selectBlog" resultMap="blogResult">
SELECT * FROM BLOG WHERE ID = #{id}
</select>
<select id="selectPostsForBlog" resultType="Post">
SELECT * FROM POST WHERE BLOG_ID = #{id}
</select>
or
<select id="selectBlog" resultMap="blogResult">
select
B.id as blog_id,
B.title as blog_title,
B.author_id as blog_author_id,
P.id as post_id,
P.subject as post_subject,
P.body as post_body,
from Blog B
left outer join Post P on B.id = P.blog_id
where B.id = #{id}
</select>
<resultMap id="blogResult" type="Blog">
<id property="id" column="blog_id" />
<result property="title" column="blog_title"/>
<collection property="posts" ofType="Post">
<id property="id" column="post_id"/>
<result property="subject" column="post_subject"/>
<result property="body" column="post_body"/>
</collection>
</resultMap>
if there is still N+1 problem in nested select like sub-query?
do you have any advice or experience of which one performs better in a certain environment or condition? thanks a lot :).
First of all a slight terminology note. Subquery in SQL is a part of the query that is a query by itself, for example:
SELECT ProductName
FROM Product
WHERE Id IN (SELECT ProductId
FROM OrderItem
WHERE Quantity > 100)
In this case the following piece of the query is the subquery:
SELECT ProductId
FROM OrderItem
WHERE Quantity > 100
So you are using term "subquery" here incorrectly. In mybatis documentation the term nested select is used.
There are two ways to fetch associated entities/collections in mybatis. Here's relevant part of the documentation:
Nested Select: By executing another mapped SQL statement that returns
the complex type desired. Nested Results: By using nested result
mappings to deal with repeating subsets of joined results.
When nested select is used mybatis executes the main query first (in your case selectBlog) and then for every record it executes another select (hence the name nested select) to fetch associated Post entities.
When Nested results are used only one query is executed but it already has the association data joined. So mybatis maps the result to the object structure.
In your example single Blog entity is returned so when nested select is used two queries are executed, but in general case (if you would get the list of Blogs) you would hit N+1 problem.
Now let's deal with performance. All the following assumes that the queries are tuned (as in there are no missing indices), you are using connection pool, the database is collocated, basically speaking your system is tuned in all other regards.
Speaking of the performance there is no single correct answer and you milage may differ. You always need to test your particular workflows in your setup. Given so many factors affect performance like data distribution (think of max/min/arg posts each blog have), the size of the record in DB (think of number and size of the data fields in blog and post), the DB parameters (like disk type and speed, amount of memory available for dataset caching etc) there may no be a single answer only some generic observations that follow.
But we can understand the performance difference if we look at the cases on the ends of the performance spectrum. Like to see cases when nested select significantly outperforms join and vice versa.
For collection fetching join should be better in most cases because network latency to do N+1 request counts.
One case when nested select may be better is for one-to-many association when the record in the main table reference some other table and the cardinality of the other table is not large and the size of the record in the other table is large.
For example, let's consider Blog has a category property that references categories table and it may have one of these values Science, Fashion, News. And let's imagine the list of blogs is selected by some filter like keywords in the blog title. If the result contains let's say 500 items then most of the associated categories would be duplicates.
If we select them with join every record in the result set would contain Category data fields (and as a reminder most of them are duplicates and we have a lot of data in Category record).
If we select them using nested select we would do the query for fetch the category by category id for every record and here mybatis session cache comes to play. For the duration of the SqlSession every time mybatis executes the query it stores its result in the session cache so it does not execute repeating requests to the database but takes them from the cache. It means that after mybatis has retrieved some category by id for the first record it would reuse it for all other records in the recordset it processes.
In the above example we would do up to 4 requests to the database but the reduced amount of the data passed over the network may overweight the need to do 4 requests.

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

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.

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.

How do I create an index online with liquibase?

I have a migration that will create an index in a table of our Oracle database. The DBA would like the index to be created ONLINE. (Something like CREATE INDEX ..... ONLINE;) Is there something I can add to the tag below to accomplish what I want or do I need to write the SQL into the migration?
<createIndex tableName="USER" indexName="IX_USER_TX_ID">
<column name="TX_ID" />
</createIndex>
There is no standard tag to specify it as ONLINE. Your 3 options to create it as ONLINE are:
Fall back to the tag where you specify the exact SQL you want
Use the modifySql tag to append to the generated SQL.
Create an extension to createIndex that always generates ONLINE at the end of the SQL or adds a parameter to createIndex that allows you to control if it is added or not.
Option #2 is probably the best mix of easy yet flexible and would look something like this:
<changeSet id="1" author="nvoxland">
<createIndex tableName="USER" indexName="IX_USER_TX_ID">
<column name="TX_ID" />
</createIndex>
<modifySql dbms="oracle">
<append value=" ONLINE"/>
</modifySql>
</changeSet>
Notice the space in the value tag. Append does a very simple text append with no built-in logic.

Resources