I've been successful in creating the following projection in combination with the @Query annotation:
Approach 1
interface Deployment{
val app: String
val platform: String
val org: String
val space: String
val instances: Int
val state: String
}
@Query(value="select a.name as app, p.name as platform, o.name as org, s.name as space, a.instances as instances, a.state as state " +
"from apps a " +
"inner join spaces s on a.space_id = s.id " +
"inner join orgs o on s.org_id = o.id " +
"inner join platforms p on o.platform_id = p.id " +
"where o.name = :eai " +
"and lower(a.name) = lower(:app) " +
"and lower(a.state) = lower(:state) " +
"order by p.name, s.name",
nativeQuery = true)
fun getDeploymentsByEaiAndAppAndState(eai: String, app: String?, state: String?): List<Deployment>
Approach 2
However, I need to dynamically create the SQL statement based on information supplied in a filter:
data class DeploymentFilter(
val eai: String
)
{
var app: String? = null
var state: String? = null
}
override fun getByFilter(filter: DeploymentFilter): List<Deployment> {
var sql =
"select a.name as app, p.name as platform, o.name as org, s.name as space, a.instances as instances, a.state as state " +
"from apps a " +
"inner join spaces s on a.space_id = s.id " +
"inner join orgs o on s.org_id = o.id " +
"inner join platforms p on o.platform_id = p.id " +
"where o.name = :eai"
if (!filter.app.isNullOrBlank()) { sql += " and lower(a.name) = lower(:app)" }
if (!filter.state.isNullOrBlank()) { sql += " and lower(a.state) = lower(:state)" }
sql += " order by p.name, s.name"
val query = em.createNativeQuery(sql)
query.setParameter("eai", filter.eai)
if (!filter.app.isNullOrBlank()) { query.setParameter("app", filter.app) }
if (!filter.state.isNullOrBlank()) { query.setParameter("state", filter.state) }
@Suppress("UNCHECKED_CAST")
return query.resultList as List<Deployment>
}
Although the code runs and executes, it's as if Spring (or Kotlin) is no longer automatically converting the Java List<Object[]> into a Kotlin List.
When I convert the List<Deployment> to JSON using Approach 1, I correctly see:
{
"eai": "6949",
"deployments": [
{
"app": "CisBidLine",
"platform": "edcbo1",
"instances": 3,
"org": "6949",
"state": "STARTED",
"space": "production"
},
...
However, with Approach 2, the Deployment data is treated as an array instead of an Object:
{
"eai": "6949",
"deployments": [
[
"CisPicture",
"edcbo1",
"6949",
"production",
1,
"STARTED"
],
...
There's no difference in the environment or logic between both approaches, other than one explicitly using @Query and the other em.createNativeQuery.
Any help would be greatly appreciated!
UPDATE
If I understand this correctly, Kotlin can effectively transform a Map to the Interface, as long as the keys of the Map match the properties of the Interface. Because query.resultList returns a List<Object[]>, the trick might be to transform that to a Map using the aliases as the keys. As I'm using Hibernate under the hood, I found the following worked:
query
.unwrap(org.hibernate.query.NativeQuery::class.java)
.setResultTransformer(org.hibernate.transform.Transformers.ALIAS_TO_ENTITY_MAP)
Now I can simply convert the Map to the Interface using:
@Suppress("UNCHECKED_CAST")
return query.resultList as List<Deployment>
For those not using Hibernate, I'm assuming one could manually convert the List to a Map first, then transform using as List<Deployment>.
I'm open to other approaches, but this seems to make reasonable sense!