Can Spring Data JDBC use object properties as query parameters? - java

Currently I use the following code for a custom SQL statement in a Spring Data JDBC Repository interface:
default void upsert(LoanRecord r) {
upsert(r.getId(), r.getUcdpDocumentFileId(), r.getFreSellerNo(),
r.getFreDeliverySellerNo(), r.getLenderLoanNo(), r.getFreLpKey());
}
#Modifying
#Query("""
INSERT INTO loan (id, ucdp_document_file_id, lender_loan_no, fre_seller_no, fre_delivery_seller_no, fre_lp_key)
values (:id, :ucdpDocumentFileId, :lenderLoanNo, :freSellerNo, :freDeliverySellerNo, :freLpKey)
ON CONFLICT (ucdp_document_file_id) DO UPDATE SET
lender_loan_no = EXCLUDED.lender_loan_no,
fre_seller_no = EXCLUDED.fre_seller_no,
fre_delivery_seller_no = EXCLUDED.fre_delivery_seller_no,
fre_lp_key = EXCLUDED.fre_lp_key""")
void upsert(UUID id, String ucdpDocumentFileId, String freSellerNo,
String freDeliverySellerNo, String lenderLoanNo, String freLpKey);
The actual statement doesn't really matter, but as you can see, there's a wrapper method that unpacks the object so I can use simple parameters in the second method. Is there some way to refer to object properties in the query (like with MyBatis) so we can get rid of the wrapper method?
For reference, my spring-data-jdbc version is 2.4.2.

This is possible with the latest milestone release of Spring Data JDBC(3.0.0-RC1!
It is probably the main use case behind SpEL support.
With it you can now use constructs like this:
#Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(#Param("customer") Customer customer);
Just as you can for a long time in Spring Data JPA.

Related

How to run a native SQL query in Spring without an entity and a JPA Repository?

I am trying to run some native SQL queries in my Spring application. I donĀ“t have an entity or JpaRepository class. I know it's strange, but this is a microservice just to collect two count queries and send it to Kafka.
Well trust me, all I need is these two integers from the queries. I run these code and always returns 0. I can see in the logs that Hikari is connecting to the database, so I don't know what to do. Searched a lot, but all answers involved the #Query solution, which does not work for me.
#Repository
#AllArgsConstructor
public class ReportRepository {
private final EntityManager em;
public int numberOfAccounts() {
String sql = "SELECT count(*) FROM account";
Query query = em.createNativeQuery(sql);
System.out.println(query.getFirstResult());
return query.getFirstResult();
}
public int numberOfSubscriptions() {
String sql = "SELECT count(*) FROM subscriptions";
Query query = em.createNativeQuery(sql);
System.out.println(query.getFirstResult());
return query.getFirstResult();
}
}
If you have EntityManager, and from what you are saying it can connect to DB, try this way:
public int numberOfSubscriptions() {
// >> "subscriptions" has to be the exact name of your table
// if does not work, consider trying SUBSCRIPTIONS or Subscriptions
String sql = "SELECT count(*) FROM subscriptions";
Query query = em.createNativeQuery(sql);
// getSingleResult() instead :)
return ((Number) query.getSingleResult()).intValue();
}
There is this (a bit old) JavaDoc for Query.getFirstResult() :
The position of the first result the query object was set to retrieve. Returns 0 if setFirstResult was not applied to the query object
So, I'd say that is not the right method for your case.
Happy Hacking :)
You should be using JDBC instead of an Entity Manager. Under the JPA uses JDBC but it requires defined entites to work. JDBC allows you to manage the connection and run the raw SQL queries.
Here's a link for how to do it in Spring:
https://spring.io/guides/gs/relational-data-access/#_store_and_retrieve_data

How to query with Spring JPA on jsonb columns?

I'm using spring JPA with PostgreSQL database. I have an Entity as follow:
#Entity
#TypeDef(name="json_binary", typeClass = com.vladmihalcea.hibernate.type.json.JsonBinaryType.class)
public class LiveTokens {
#Id
#GeneratedValue()
private Long id;
private String username;
#Type(type="json_binary")
#Column(columnDefinition = "jsonb")
private Token token
}
and Token:
public class Token {
private Long expireTime;
private String accessToken;
}
For saving object to column with Hibernate, I use Hibernate Types project.
Now I want to get All LiveTokens that expired. I can't do it with Spring JPA. How do I query to posgresql jsonb column with Spring data JPA?
SQL JSON functions
If you want to call a SQL function that processes the JSON column, then you just need to make sure you register the function with Hibernate prior to using it.
JSON operators
For JSON operators, like the PostgreSQL ones (e.g., ->>), the only option you have is to use a native SQL query. JPQL or HQL don't support database-specific JSON operators.
Using EclipseLink and spring data jpa, if your data in db is something like: {"expireTime":102020230201, "accessToken":"SOMETHING" }, my first question is why to use long numbers for your dates instead of timestamps (ex '2019-09-14 12:05:00'). If you use timestamps there are also options to manage timezones (either from postgresql or from you source code).
Regarding your issue you may use the FUNC JPQL keyword of EclipseLink (Hibernate may have something similar) in order to run a database specific function. In the example below I use FUNC('jsonb_extract_path_text', lt.token, 'expireTime') to get the values of the json for token.expireTime.
PostgreSql method jsonb_extract_path_text returns text, thus you cannot do a less that condition, so I cast the output of the function using JPQL CAST keyword with (CAST -data- TO -type-).
#Repository
public interface MyRepository extends JpaRepository<LiveTokens, Integer> {
#Query(value = "SELECT lt FROM LiveTokens lt WHERE CAST(FUNC('jsonb_extract_path_text', lt.token, 'expireTime') AS LongType) < :expirirationThreshold")
List<LiveTokens> findByExpireTime(#Param("expirirationThreshold") Long expirirationThreshold);
}
Again, this is not tested.
This is how a native query using postgres JSON query operators would look like, incoorporating your example:
#Query(value="SELECT t.* FROM LiveTokens t WHERE CAST(t.token ->> 'expireTime' AS LONG) < now()", native=true)
Assuming your real tablename is LiveTokens, native queries do no longer use the JPA translations, and the tablename has to match the one in the DB. (You may also need to specify it's schema.)
Try it:
Service class:
Long currentTime = new Date().getTime();
Repository:
#Query("SELECT lt FROM LiveTokens lt WHERE lt.token.expireTime <= :currentTime")
List<LiveTokens> findExpiredLiveTokens(#Param("currentTime") long currentTime)

Why need #Query when I can write a method in Spring JPA

I just got started with Spring JPA and I am reviewing code written by my coworker.
I see that he has been using the following code to find a Login object using username:
public interface LoginDao extends JpaRepository<Login, Long> {
#Query("SELECT u FROM Login u WHERE LOWER(u.username) = LOWER(:username)")
Login findByUsername(#Param("username") String username);
}
Cant he just create a method like this:
#GET
#Path("{username}")
public Login getOne(#PathParam("username") String username) {
Login login = loginDao.findOne(username);
if (login == null) {
throw new WebApplicationException(Response.Status.NOT_FOUND);
} else {
return login;
}
}
What are the fundamental advantages of using #Query rather than writing a method approach. Or am I plain wrong and both have different purposes.
I personally dont want to write queries inside the code. I think mixing java with sql queries can make code look uglier.
Our stack is java, JPA, Jersey, JAX-RS, Postgreql, Spring Boot, Hibernate
Regards
First, This is not an SQL query, this is a JPQL one. It would be a native SQL query if the nativeQuery attribute was set to true, which is not the case by default.
It is perfectly fine to create queries with JPQL, you will be able to switch from Hibernate to Eclipse Link or another JPA implementation without issues. You should also be able to switch from Postgres to MySQL, SQL Server...
You have to start to worry if your coworker creates queries with #Query(nativeQuery = true, value = "..."), otherwise it looks fine to me.
Second, when you look to your repository declaration, you can see that the ID field for your Login entity is of type Long. That means the loginDao.findOne(..) method wants a Long parameter. Probably a surrogate key which is not username.
This is why your coworker created this new method: to find a Login row by something else than the Long surrogate key. (Here by username which is most likely a business key, but I do not know your application.)
Just for your information: Spring automatically creates queries with the signature of your repository methods (derived queries). For example:
public interface LoginDao extends JpaRepository<Login, Long> {
Login findByUsername(String username);
}
Spring JPA will automatically create a query looking for a field named username and create a WHERE clause with it.
Notice that it is not the same than your coworker query because it will not use the LOWER function.
Some anchors in the JPA documentation about those derived queries:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repositories.query-methods
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-keywords
Another thing about your title:
"Why need #Query when I can write a method in Spring JPA"
Well this #Query writes a method in Spring JPA. :^)
JPARepository has come up with some of the Supported keywords which will write the queries themselves based on your entity.
If we are looking something out of box from what JPARepository provides #Query is useful like - Joining multiple queries, writing native queries etc.
From your code snippet both would do the same
For more info you can refer - https://docs.spring.io/spring-data/jpa/docs/1.4.2.RELEASE/reference/html/jpa.repositories.html

How to prevent SQL Injection in Hibernate via POJO

We are using Hibernate on Tomcat. We recently found a SQL injection vulnerability in our Hibernate code. Specifically in reguards to the POJOs we use for ORM.
We are taking in the user input and creating a new POJO like this:
//POJO associated with Foo.hib.xml
public class Foo{
private String a;
private String b;
public Foo(String a, String b){
this.a=a;
this.b=b;
}
//Getters go here
}
We have many many pojos like this that are used in various rest services across various parts of our tomcat platmform. The problem we found is that users can set a sql query as a value for Foo so when we create it and save it, they can do sql injection:
//example of possible injection
Foo foo = new Foo("select * from *;", "insert * into * as *");
//save new Foo to hibernate
session.saveOrUpdate(foo); //above queries will be executed on insert
The above is just a generic example of what we are seeing.
I have looked all over and so far all I have found is examples for sanitizing data on queries and not on insert values through hibernate. Is there a way through hibernate to have it sanitize the data of a pojo before inserting it into the db?
Simple Answer:
You need to esnure that you are NOT concatenating the HQL/SQL query strings with input data directly like below:
//Unsafe Hibernate query, Never do this
Query query = session.createQuery(" from Employee where empId='"+inputEmpId+"'");
Rather, you need to set the data using setParameter() methods provided by org.hibernate Query API (or for JDBC PraparedStatements use setString(""), etc..) like below:
//Safe Hibernate query
Query query = session.createQuery(" from Employee where empId=:empId");
query.setParameter("empId", inputEmpId);
For long answer, you can look at here.

How exactly works this implementation of the query() method of the Spring JdbcTemplate?

I am working on a Spring application that use JdbcTemplate to query the database and I have some doubts about how exactly it works.
So, into a service class, I have this method definition that performs a query:
//JDBC TEMPLATE SELECT EXAMPLE
public List<DBLog> queryAllLogs() {
System.out.println("JDBCExample: queryAllLogs() is called");
final String QUERY_SQL = "SELECT * FROM LOG ORDER BY IDLOG";
List<DBLog> dbLogList = this.jdbcTemplate.query(QUERY_SQL, new RowMapper<DBLog>() {
public DBLog mapRow(ResultSet resulSet, int rowNum) throws SQLException {
System.out.println("Getting log: "+ rowNum + " content: " + resulSet.getString("LOGSTRING"));
DBLog dbLog = new DBLog();
dbLog.setIDLOG(resulSet.getInt("IDLOG"));
dbLog.setLOGSTRING(resulSet.getString("LOGSTRING"));
return dbLog;
}
});
return dbLogList;
}
This method simply perform a query that return all the records inside a LOG table orderd by the IDLOG field value. This is pretty simple to understand.
Reading on the official documentation I found that this implementation of the query() method take 2 objects: the query string and a RowMapper object and that:
Query using a prepared statement, mapping each row to a Java object
via a RowMapper
So I think that the QUERY_SQL query String is automatically converted into a PreparedStatment by the query() method implementation (is it right or am I missing something?)
The thing that is absolutly not clear for me is that it seems to me that in the previous example, I am defining a RowMapper implementation as second parameter of the query method.
So this specific implemention contain the mapRow(ResultSet resulSet, int rowNum) method implementation that from what I have understand is called for each row of the ResultSet object returned by the query execution. So this method will automatically map a specific row on a DBLog that will be automatically added to the returned List<DBLog> dbLogList list.
Is it my reasoning correct or am I missing something?
Who do all this work? Is it this specific query() method implementation (the one that take these 2 specific input parameter) that take care to call the mapRow() method of the passed RowMapper object and then add the returned DBLog object to the list?
Your reasoning is entirely correct.
First, take a look at this table in the documentation. It lists everything that Spring JDBC automatically does and what you are left to do. Basically, all you need to do when using Spring JDBC is:
setting up the JDBC connection
specify the SQL statement to execute
provide parameters value if there are parameters
for each result, do the work of converting the result to an object
JDBCTemplate.query(String, RowMapper) follows the same pattern. First, you give it a SQL statement to execute: this is the first parameter (point 2 of the list above). Second, you give it an object which will be responsible for translating each result into your domain object (point 4 of the list above).
This object is called a RowMapper because it maps rows of a database, represented by a ResultSet object, into your domain object.
This is one of the main advantage of using Spring JDBC over raw JDBC: it factors out all the common and repeating tasks into its core. Yes, it will use a PreparedStatement under the hood, that is going to be executed and the ResultSet will be looped over. And in each iteration of this loop (made by Spring JDBC), your RowMapper will be called. Its result will be aggregated into a List by Spring JDBC and finally returned.

Categories