Checking for nullable columns from a JdbcTemplate with Spring and Kotlin - spring-boot

In my Spring application with Kotlin, I am reading a sql table that has many nullable columns. For a nullable column I using this if-expression:
if (rs.getObject("ordernumber") != null) rs.getInt("ordernumber") else null
Is there an easier way than writing an if-expression for each nullable column?
I have simplified the example to one nullable column, but of course I have many more nullable columns with String, Integer, Timestamp and so on.
fun getEmployeePayRecord(employee: Employee): List<EmployeePayRecord> {
val rowMapper: RowMapper<EmployeePayRecord> = RowMapper { rs, _ ->
EmployeePayRecord(
uuid = rs.getString("uuid"),
workingDay = rs.getTimestamp("working_day").toLocalDateTime(),
orderNumber = if (rs.getObject("ordernumber") != null) rs.getInt("ordernumber") else null
)
}
return jdbcTemplate.query(
"""select uuid
, working_day
, ordernumber
from plrv11.employee_pay_record
where employee_number = :employeeNumber
order by working_day
""", rowMapper, employee.employeeNumber
).toList()
}
EDIT
I have taken up M. Deinum's and David's ideas and added extension functions, like this:
fun ResultSet.getIntOrNull(columnName: String): Int? {
val result = getInt(columnName)
return if (wasNull()) null else result
}

Related

Kotlin MVVM, How to get the latest value from Entity in ViewModel?

I have created an app where I try to insert a record with the latest order number increased by one.
The main function is triggered from Activity, however, the whole process is in my ViewModel.
Issue no 1, After I insert a new record the order by number is not updated.
Issue no 2, When I insert first record the order by number is null, for that reason I am checking for null and setting the value to 0.
My goal here is to get the latest order_by number from Entity in my ViewModel, increased by 1 and add that new number to my new record using fun addTestData(..).
Entity:
#Entity(tableName = "word_table")
data class Word(
#ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "order_by") val orderBy: Int
Dao:
#Query("SELECT order_by FROM word_table ORDER BY order_by DESC LIMIT 1")
suspend fun getHighestOrderId(): Int
Repository:
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun getHighestOrderId(): Int {
return wordDao.getHighestOrderId()
}
ViewModel:
private var _highestOrderId = MutableLiveData<Int>()
val highestOrderId: LiveData<Int> = _highestOrderId
fun getHighestOrderId() = viewModelScope.launch {
val highestOrderId = repository.getHighestOrderId()
_highestOrderId.postValue(highestOrderId)
}
fun addTestData(text: String) {
for (i in 0..1500) {
getHighestOrderId()
var highestNo = 0
val highestOrderId = highestOrderId.value
if (highestOrderId == null) {
highestNo = 0
} else {
highestNo = highestOrderId
}
val addNumber = highestNo + 1
val word2 = Word(0, text + "_" + addNumber,addNumber)
insertWord(word2)
}
}
Activity:
wordViewModel.addTestData(text)

Spring JPA #Query : check if parameter is null before to use it - bad hibernate translation?

I am currently facing an issue with JPA Query.
I have a class Event and a class Race, an event has a list of races.
Event and Race have an attribute type Point from lib org.locationtech.jts.geom.Point to handle Geometry object of my PostgreSQL (extended with PostGis) database.
public class Event {
#OrderBy("rank")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "event", orphanRemoval = true)
protected List<Race> races;
private Point locapoint;
...
}
public class Race {
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JsonIgnore
private Event event;
private boolean hasAddressEvent;
private Point locapoint; //null if hasAddressEvent is true
...
}
This query works fine in PGSQL (extended with Postgis) :
SELECT distinct e.* FROM Event e inner join Race r on e.id = r.event_id
where ((:radius is null or :point is null)
or (r.has_address_event = TRUE and st_distancesphere(:point, e.locapoint) < :radius)
or (r.has_address_event = FALSE and st_distancesphere(:point, r.locapoint) < :radius))
it returns all events that have at least one race located in the circle drawn with these two entries :point (example of value : ST_Point(3.2727,50.2788)) and :radius (integer that represents meters).
Problem is to use it in #Query JPA in my EventRepository :
#Query(value = "SELECT distinct e.* "
+ "FROM Event e inner join Race r on e.id = r.event_id " +
"where " +
"((:radius is null or :point is null) " +
"or (r.has_address_event = TRUE and st_distancesphere(:point, e.locapoint) < :radius) " +
"or (r.has_address_event = FALSE and st_distancesphere(:point, r.locapoint) < :radius))"
, nativeQuery = true)
Page<Event> search(Pageable page, #Param("point") Point point, #Param("radius") Integer radius);
It works only if point and radius params are not null.
For example if radius is null, I have this error :
PSQLException: ERROR: operator does not exist : double precision < bytea
It's like if conditions where not short-circuited : even if the first condition returns false it continues to treat the condition.
(:radius is null or :point is null)
With some research, I found this :
http://randomthoughtsonjavaprogramming.blogspot.com/2015/10/making-mistakes-in-jpql.html
This lets me think that the problem is my query is turning in SQL and completly changing my condition's structure.
I tried to do equivalent query in JPQL (instead of native PGSQL) and it's the same result.
How can I fix it or just workaround.
Sorry for my english
Thanks in advance,
Thomas
EDIT :
As suggest #hqrd, I can do the null condition treatment inside my service and use two differents queries :
default Page<Event> search(Pageable page, Point point, Integer radius) {
if (point == null || radius == null) {
return findAll(page);
} else {
return searchAround(page, point, radius);
}
}
It will be a good solution for the example I introduced in my post.
But this query is just a POC, my final query will be more sophisticated (with more conditions, with 6 parameters that can potentially be null).
I don't know how to solve this in the #Query, but I suggest you split your issue using java.
#Query(value = "SELECT distinct e.* FROM Event e inner join Race r on e.id = r.event_id " +
"where (r.has_address_event = TRUE and st_distancesphere(:point, e.locapoint) < :radius) " +
"or (r.has_address_event = FALSE and st_distancesphere(:point, r.locapoint) < :radius))"
, nativeQuery = true)
Page<Event> searchAround(Pageable page, #Param("point") Point point, #Param("radius") Integer radius);
default Page<Event> search(Pageable page, Point point, Integer radius) {
if (point == null || radius == null) {
return findAll(page);
} else {
return searchAround(page, point, radius);
}
}
This way you have more control on your requests.

Can #SqlResultSetMapping be used to map a complex Dto object

I currently have a named native query set up in CrudRepository where I'm joinnig few tables and I need to map that query result into a Dto.
select
event_id, replaced_by_match_id, scheduled, start_time_tbd, status, away_team_competitor_id, home_team_competitor_id, round_round_id, season_season_id, tournament_tournament_id, venue_venue_id,
competitorHome.competitor_id as home_competitor_competitor_id, competitorHome.abbreviation as home_competitor_competitor_abbreviation, competitorHome.country_code as home_competitor_ccountry_code, competitorHome.ioc_code as home_competitor_ioc_code, competitorHome.rotation_number as home_competitor_rotation_number, competitorHome.virtual as home_competitor_virtual,
competitorAway.competitor_id as away_competitor_competitor_id, competitorAway.abbreviation as away_competitor_competitor_abbreviation, competitorAway.country_code as away_competitor_ccountry_code, competitorAway.ioc_code as away_competitor_ioc_code, competitorAway.rotation_number as away_competitor_rotation_number, competitorAway.virtual as away_competitor_virtual,
homeTeamTranslation.competitor_competitor_id as home_team_translation_competitor_competitor_id, homeTeamTranslation.language_language_id as home_team_translation_language_language_id, homeTeamTranslation.competitor_name as home_team_translation_competitor_name, homeTeamTranslation.competitor_country as home_team_competitor_country,
awayTeamTranslation.competitor_competitor_id as away_team_translation_competitor_competitor_id, awayTeamTranslation.language_language_id as away_team_translation_language_language_id, awayTeamTranslation.competitor_name as away_team_translation_competitor_name, awayTeamTranslation.competitor_country as away_team_competitor_country
from "event" as e
left join competitor as competitorAway on competitorAway.competitor_id = e.away_team_competitor_id
left join competitor as competitorHome on competitorHome.competitor_id = e.home_team_competitor_id
left join competitor_translation as homeTeamTranslation on competitorHome.competitor_id = homeTeamTranslation.competitor_competitor_id
left join competitor_translation as awayTeamTranslation on competitorAway.competitor_id = awayTeamTranslation.competitor_competitor_id
where awayTeamTranslation.language_language_id = 'en' and homeTeamTranslation.language_language_id = 'en'
I'm trying to use #SqlResultSetMapping annotation to map result into Dto classes but unsuccessfully.
I've set up mapping this way
#SqlResultSetMapping(
name = "mapLocalizedEvent",
classes = [ConstructorResult(
targetClass = TranslatedLocalEvent::class,
columns = arrayOf(
ColumnResult(name = "event_id"),
ColumnResult(name = "scheduled"),
ColumnResult(name = "start_time_tbd"),
ColumnResult(name = "status"),
ColumnResult(name = "replaced_by_match_id")
)
)]
)
and it is working fine where all of the ColumnResult used are simple types String or Boolean. It maps to object TranslatedLocalEvent looking like this
class TranslatedLocalEvent(
val eventId: String? = null,
val scheduled: String? = null,
val startTimeTbd: Boolean? = null,
val status: String? = null,
val replacedByMatchId: String? = null
)
Is there a way I can use this approach to map a complex object? TranslatedLocalEvent object needs to contain TranslatedLocalCompetitor object built from parts of columns query returnes
class TranslatedLocalEvent(
val eventId: String? = null,
val scheduled: String? = null,
val startTimeTbd: Boolean? = null,
val status: String? = null,
val replacedByMatchId: String? = null,
val homeTeam: TranslatedLocalCompetitor? = null
)
public class TranslatedLocalCompetitor(
val competitorId: String? = null
val competitorName: String? = null
val competitorCountry: String? = null
)
The easiest way i see is in your TranslatedLocalEvent constructor accept all columns and in the contstructor create and assign the TranslatedLocalCompetitor object.

Adding parameters optionally to Spring Data JPA Native query

I am using Spring Data JPA with native queries like below
public interface ItemRepository extends JpaRepository<ItemEntity, Long> {
#Query(value = "select * from items i where i.category = :itemCategory and i.item_code = :itemCode", nativeQuery = true)
Page<ItemEntity> search(#Param("itemCategory") String itemCategory, #Param("itemCode") String itemCode, Pageable pageable);
}
Now, my use case is
It itemCode is available, only the item with that code from that category should be returned.
But if itemCode is not available then all items in that category should be returned.
So the problem with the above category is when itemCode is passed as NULL no data is returned since it does not match anything. whereas the requirement is it should be ignored.
So is there a way to optionally add a clause to Spring Data JPA native query. I know it is possible with CriteriaQuery but can we do something similar for native queries?
Thanks
Yes, it's feasible with native query too. Very Good explanation here read this
#Approach1
#NamedQuery(name = "getUser", query = "select u from User u"
+ " where (:id is null or u.id = :id)"
+ " And :username"
:itemCode is null or i.item_code = :itemCode
#Approach2
# UserDao.java
public User getUser(Long id, String usename) {
String getUser = "select u from user u where u.id " + Dao.isNull(id)
+ " And u.username " + Dao.isNull(username);
Query query = Dao.entityManager.createQuery(getUser);
}
# Dao.java
public static String isNull(Object field) {
if (field != null) {
if (field instanceof String) {
return " = " + "'" + field + "'";
} else {
return " = " + field;
}
} else {
return " is NULL ";
}
}
How to handle null value of number type in JPA named query
Just modify the where condition
i.item_code = :itemCode
to
:itemCode is null or i.item_code = :itemCode

How to test null value on nullable enum

I have documents with property _report_type, it supposed to contain a string "FTE", or "CAB".
In legacy document, it can be null or not exist at all (undefined), they are supposed to be "ContractorWorkReport".
My mapping:
public class Order : Document, IBaseEntity<string>
{
[JsonProperty(Order = 70, PropertyName = "orderData")]
public OrderData Data { get; set; }
}
public class OrderData
{
[JsonProperty(Order = 60, PropertyName = "_order_type")]
public OrderType? OrderType { get; set; }
}
[JsonConverter(typeof(StringEnumConverter))]
public enum OrderType
{
[EnumMember(Value = "TFE")]
ContractorWorkReport,
[EnumMember(Value = "CAB")]
BudgetElectricMeter
}
My query based on that :
var query = _client.CreateDocumentQuery<T>($"/dbs/{_databaseId}/colls/{CollectionId}");
if (filter.OrderType.Value == OrderType.ContractorWorkReport)
query = query.Where(o => o.Data.OrderType == null || !o.Data.OrderType.HasValue || o.Data.OrderType == filter.OrderType);
else
query = query.Where(o => o.Data.OrderType == filter.OrderType);
This query crashes because of the o.Data.OrderType == null, error message is :
Unable to cast object of type 'Microsoft.Azure.Documents.Sql.SqlNullLiteral' to type 'Microsoft.Azure.Documents.Sql.SqlNumberLiteral'.'
How can fix this? Right now, i do this, but it's dirty...
if (filter.OrderType.Value == OrderType.ContractorWorkReport)
query = query.Where(o => o.Data.OrderType != OrderType.BudgetElectricMeter);
else
query = query.Where(o => o.Data.OrderType == filter.OrderType);
Thanks!
First, I would recommend you use this for the first parameter in CreateDocumentQuery : UriFactory.CreateDocumentCollectionUri(databaseId, collectionId)
To answer your question, you are comparing an int to a null. o.Data.OrderType, being an enum, resolves to an int. You may want to compare that to the default enum value 0 or query over documents where o.Data.OrderType is not a key at all

Resources