How can optimised the code for Derived Query Methods in Spring Data JPA Repositories - spring-boot

Entity Class:
#Document(collection = “personErasure")
class PersonErasureModel {
#Id
var id: UUID = UUID.randomUUID()
lateinit var personRequestId: UUID
var fName: String? = null
var validationState: PersonValidationState? = null
var erasureState: ErasureState? = null
}
//1. Enum class
enum class PersonValidationState {
PROCESS,
ELIGIBLE,
INELIGIBLE
}
//2. Enum Class
enum class ErasureState {
IN_PROGRESS,
COMPLETED
}
Repository:
#Repository
interface PersonErasureRepository : ReactiveMongoRepository<PersonErasureModel, UUID> {
fun countByPersonRequestIdAndValidationStateIn(
personRequestId: UUID,
statuses: List<PersonValidationState>
): Mono<Long>
fun countByPersonRequestIdAndErasureState(
personRequestId: UUID,
erasureState: ErasureState
): Mono<Long>
}
Here in PersonErasureRepository both methods are similar. But both PersonValidationState and ErasureState are two different properties in model class.
Can we use a single method which can handle both the use case? Then how can name the Derived query method ?

Related

Any benefit of adding a read Query in a service class in Spring

There is a code where there are single read query in a service class , does this have any effect in terms of performance/internal way hibernate runs the query ? As it's easier to just write the query in a facade/handler layer than a call the service class in read only ops
Run with hibernate here and PersonRepo is the envers class for the Person class annotated with #Entity
For eg)
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
#Service
class PersonService(
private val personRepo: PersonRepo
) {
fun getPersonById(personId: Long): PersonEntity {
return this.personRepo.findById(personId)?: throw ValidationException("person $personId does not exist")
}
}
when writing this query in handler/non service layer when should we
call it from a service class
#Component
class PersonHandler (
private val personRepo: PersonRepo,
private val personService: personService,
private val someOtherHandler: SomeOtherHandler
) {
fun displayPersonAge(personId: Long) {
val person = this.personService.getPersonById(person)
someOtherHandler(person)
}
}
get the object directly
#Component
class PersonHandler (
private val personRepo: PersonRepo,
private val personService: personService,
private val someOtherHandler: SomeOtherHandler
) {
fun displayPersonAge(personId: Long) {
val person = this.personRepo.findById(personId)?: throw ValidationException("person $personId does not exist")
someOtherHandler(person)
}
}
since both are one line here in kotlin , does adding it to a serviceClass have any benefit to it ? if it is run in a transaction/not in run in a transaction are there any effects on other db transactions and query performance under load

How to initialize variables in parent abstract class of spring bean using Kotlin?

I have the next structure of spring beans
abstract class GenericRepository<T> {
private val FIND_BY_ID_SQL = "SELECT * FROM ${this.getTableName()} WHERE id = ?"
abstract fun getTableName(): String
abstract fun jdbcTemplate(): JdbcTemplate
abstract fun getMapper(): RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate().queryForObject(FIND_BY_ID_SQL, arrayOf(id), getMapper())
}
}
User repository
#Repository
class UserRepository(
#Autowired
private val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>() {
companion object {
private const val INSERT_SQL = "INSERT INTO \"user\"(name, age) VALUES (?,?)"
}
private class LogMapper : RowMapper<User> {
override fun mapRow(rs: ResultSet, rowNum: Int): User? {
return User(
id = rs.getLong("id"),
name = rs.getString("name"),
age = rs.getInt("operation")
)
}
}
override fun getTableName(): String {
return "user"
}
override fun jdbcTemplate(): JdbcTemplate {
return jdbcTemplate
}
override fun getMapper(): RowMapper<User> {
return LogMapper()
}
}
The problem when Spring creates proxy and creates bean of UserRepository it doesn't initialize FIND_BY_ID_SQL leaving it null.
The question: how usign abstract class make spring initialize FIND_BY_ID_SQL variable?
UPD
I used #Component instead of #Repository and the problem was solved. FIND_BY_ID_SQL is not null anymore.
You could work around the problem by making it lazy:
private val FIND_BY_ID_SQL by lazy { "SELECT * FROM ${this.getTableName()} WHERE id = ?" }
However, you should first be sure it's an actual problem (e.g. that when you call find you get an exception), because the proxy might simply delegate to a "real" UserRepository with non-null FIND_BY_ID_SQL (and jdbcTemplate etc.), depending on Spring's internal details.
In addition, you need to be careful when your superclass properties are initialized depending on subclass; I think your exact situation should work, but I'd prefer to write it as
abstract class GenericRepository<T>(val tableName: String) {
private val FIND_BY_ID_SQL = "SELECT * FROM ${tableName} WHERE id = ?"
abstract val jdbcTemplate: JdbcTemplate
abstract val mapper: RowMapper<T>
fun find(id: Long): T? {
return jdbcTemplate.queryForObject(FIND_BY_ID_SQL, arrayOf(id), mapper)
}
}
#Repository
class UserRepository(
#Autowired
override val jdbcTemplate: JdbcTemplate
) : GenericRepository<User>("user") { ... }

Blocking problem of graphql-kotlin with DataLoader&BatchLoader

I used the framework "https://github.com/ExpediaGroup/graphql-kotlin" to learn graphql programming of kotlin under springframework.
I used DataLoader&BatchLoader to resolve the 'N+1' loading problem.
When the scope of DataLoader objects is singleton, it works, but it's not my goal because temporary cache mechanism should not be bridging over different requests.
Then I changed the scope of DataLoader objects to be prototype, in all probability, the graphql query may be blocking and associated objects will not be loaded, client waits the response forever.
What's the reason and how can I resolve it?
I did it like this:
Create a simple springboot application, add maven dependency of graph-kotlin
<dependency>
<groupId>com.expediagroup</groupId>
<artifactId>graphql-kotlin-spring-server</artifactId>
<version>2.0.0.RC3</version>
</dependency>
Create two model classes(Note: Their code will be changed in the final step)
data class Department(
val id: Long,
val name: String
)
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
)
Create two mocked repository objects
val DEPARTMENTS = listOf(
Department(1L, "Develop"),
Department(2L, "Test")
)
val EMPLOYEES = listOf(
Employee(1L, "Jim", 1L),
Employee(2L, "Kate", 1L),
Employee(3L, "Tom", 2L),
Employee(4L, "Mary", 2L)
)
#Repository
open class DepartmentRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(DepartmentRepository::class.java)
}
open fun findByName(namePattern: String?): List<Department> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
DEPARTMENTS.filter { it.name.contains(pattern) }
}
?: DEPARTMENTS
open fun findByIds(ids: Collection<Long>): List<Department> { // For assciation
LOGGER.info("BatchLoad departments by ids: [${ids.joinToString(", ")}]")
return DEPARTMENTS.filter { ids.contains(it.id) }
}
}
#Repository
open class EmployeeRepository {
companion object {
private val LOGGER = LoggerFactory.getLogger(EmployeeRepository::class.java)
}
open fun findByName(namePattern: String?): List<Employee> = //For root query
namePattern
?.takeIf { it.isNotEmpty() }
?.let { pattern ->
EMPLOYEES.filter { it.name.contains(pattern) }
}
?: EMPLOYEES
open fun findByDepartmentIds(departmentIds: Collection<Long>): List<Employee> { // For association
LOGGER.info("BatchLoad employees by departmentIds: [${departmentIds.joinToString(", ")}]")
return EMPLOYEES.filter { departmentIds.contains(it.departmentId) }
}
}
Create a graphql query object to export root query operations
#Service
open class OrgService(
private val departmentRepository: DepartmentRepository,
private val employeeRepository: EmployeeRepository
) : Query {
fun departments(namePattern: String?): List<Department> =
departmentRepository.findByName(namePattern)
fun employees(namePattern: String?): List<Employee> =
employeeRepository.findByName(namePattern)
}
Create an abstract class for many-to-one associated object loading
abstract class AbstractReferenceLoader<K, R: Any> (
batchLoader: (Collection<K>) -> Collection<R>,
keyGetter: (R) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, R?>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.associateBy(keyGetter)
.let { map ->
keys.map { map[it] }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create an abstract class for one-to-many associated collection loading
abstract class AbstractListLoader<K, E>(
batchLoader: (Collection<K>) -> Collection<E>,
keyGetter: (E) ->K,
optionsInInitializer: (DataLoaderOptions.() -> Unit) ? = null
): DataLoader<K, List<E>>(
{ keys ->
CompletableFuture.supplyAsync {
batchLoader(keys)
.groupBy(keyGetter)
.let { map ->
keys.map { map[it] ?: emptyList() }
}
}
},
optionsInInitializer?.let {
DataLoaderOptions().apply {
this.it()
}
}
)
Create annotation to let spring manager DataLoader beans by prototype scope
#Retention(RetentionPolicy.RUNTIME)
#Target(AnnotationTarget.CLASS)
#Component
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
annotation class DataLoaderComponent
Create loader bean to load the parent object reference of Employee object
#DataLoaderComponent
open class DepartmentLoader(
private val departmentRepository: DepartmentRepository
): AbstractReferenceLoader<Long, Department>(
{ departmentRepository.findByIds(it) },
{ it.id },
{ setMaxBatchSize(256) }
)
Create loader bean to load the child object collection of Department object
#DataLoaderComponent
open class EmployeeListByDepartmentIdLoader(
private val employeeRepository: EmployeeRepository
): AbstractListLoader<Long, Employee>(
{ employeeRepository.findByDepartmentIds(it) },
{ it.departmentId },
{ setMaxBatchSize(16) }
)
Create an GraphQL configuration to let 'graphql-kotlin' know all the DataLoader beans
#Configuration
internal abstract class GraphQLConfig {
#Bean
open fun dataLoaderRegistryFactory(): DataLoaderRegistryFactory =
object: DataLoaderRegistryFactory {
override fun generate(): DataLoaderRegistry = dataLoaderRegistry()
}
#Bean
#Scope(
ConfigurableBeanFactory.SCOPE_PROTOTYPE,
proxyMode = ScopedProxyMode.NO
)
protected open fun dataLoaderRegistry(
loaders: List<DataLoader<*, *>>
): DataLoaderRegistry =
DataLoaderRegistry().apply {
loaders.forEach { loader ->
register(
loader::class.qualifiedName,
loader
)
}
}
#Lookup
protected abstract fun dataLoaderRegistry(): DataLoaderRegistry
}
Add tow methods into DataFetchingEnvironment to get DataLoader objects
inline fun <reified L: AbstractReferenceLoader<K, R>, K, R> DataFetchingEnvironment.getReferenceLoader(
): DataLoader<K, R?> =
this.getDataLoader<K, R?>(L::class.qualifiedName)
inline fun <reified L: AbstractListLoader<K, E>, K, E> DataFetchingEnvironment.getListLoader(
): DataLoader<K, List<E>> =
this.getDataLoader<K, List<E>>(L::class.qualifiedName)
Change the code of Department and Employee, let them support association
data class Department(
val id: Long,
val name: String
) {
suspend fun employees(env: DataFetchingEnvironment): List<Employee> =
env
.getListLoader<EmployeeListByDepartmentIdLoader, Long, Employee>()
.load(id)
.await()
}
data class Employee(
val id: Long,
val name: String,
#GraphQLIgnore val departmentId: Long
) {
suspend fun department(env: DataFetchingEnvironment): Department? =
env
.getReferenceLoader<DepartmentLoader, Long, Department>()
.load(departmentId)
.await()
}
Build & Run
Start the SpringBoot applications, open http://locathost:8080/playground.
Execute the query, the result may be success or failed!
{
employees {
id
name
department {
id
name
employees {
id
name
}
}
}
}
If it is success, the client can get the response, and the server log is
2020-03-15 22:47:26.366 INFO 35616 --- [onPool-worker-5] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]
2020-03-15 22:47:26.367 INFO 35616 --- [onPool-worker-5] org.frchen.dal.EmployeeRepository : BatchLoad employees by departmentIds: [1, 2]
If it is failed, the client is blocking and waits for the response forever, and the server log is
2020-03-15 22:53:43.159 INFO 35616 --- [onPool-worker-6] org.frchen.dal.DepartmentRepository : BatchLoad departments by ids: [1, 2]

How to use another variable name or How to flatten entity in JPA Projection for nested object

I'm making an api for querying nested entity with Spring Data JPA Projection.
My Code
The Entity:
#Entity
class User {
#Id
var userId: String
var name: String
var age: Int
#OneToOne
#JoinColumn(name = "userId")
var address: Address
}
#Entity
class Address {
var userId: String
var street: String
var city: String
var country: String
}
The Repository:
interface UserView {
val name: String
val address: AddressView
interface AddressView {
val city: String
val country: String
}
}
#Repository
interface UserRepository : JPARepository<User, String> {
fun findAll(): List<UserView>
}
Expected Response
{
"name": "example",
"city": "example-city",
"country": "example-country"
}
My code produces
{
"name": "example",
"address": {
"city": "example-city",
"country": "example-country"
}
}
I Tried
I tried another view to flatten object:
interface UserView {
val name: String
val addressCity: String
val addressCountry: String
}
But this case, the variable naming is too complicate.
I want to solve this problem with projection. How can I solve this problem?
In JPA, you can do this using #NamedNativeQuery only:
#NamedNativeQuery(
name = "getUser",
query = "SELECT u.name, a.city, a.country FROM User u and Address a where a.userId = u.id ", resultClass=UserView .class)
#Entity
class User {
...
}
For Reference hibernate-named-query
Try this:
data class UserView(name: String, city: String, country: String)
#Repository
interface UserRepository : JPARepository<User, String> {
#Query(value = "select new your.pkg.UserView(u.name, u.address.city, u.address.country) from User u")
fun findAllFlat(): List<UserView>
}
You can use #Value and combine more fields or even access the fields of objects.
From the spring-data-rest documentation:
You can create a projection that combines the two data fields in the preceding example together, as follows:
#Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
#Value("#{target.firstName} #{target.lastName}")
String getFullName();
}
Spring’s #Value annotation lets you plug in a SpEL expression that takes the target object and splices together its firstName and lastName attributes to render a read-only fullName.
Flattening also works for me:
#Value("#{target.document.title}")
String getDocumentTitle();

lateinit property S has not been initialized

I want inject a singleton in another class by kotlin in spring boot.
S.kt
#Singleton
#Component
class S(
private val userService: UserService,
val companyRepo: CompanyRepo
)
WorkingGroup.kt
class WorkingGroup(
override val name: String = "",
override val desc: String = ""
) : Csv() {
fun isCompatible(ct2: WorkingGroup): Boolean = this == ct2
companion object : ICsvEnumCompanion<WorkingGroup> {
#Inject
private lateinit var s: S
override val VALUES: List<WorkingGroup>
by lazy {
val details = s.user().company.details ?: CompanyDetails()
details.workingGroups.map { WorkingGroup(it.name, it.desc) }
}
}
}
By this code, I get below error:
Caused by: org.zalando.problem.DefaultProblem: Internal Server Error: lateinit property s has not been initialized
I search for this error and found some result like this, but the problem not solved.
How can I inject service in companion object in kotlin?
In order for Spring to inject into a companion object you will need to create a setter for the field outside of the companion object. WorkingGroup will need to be a Spring managed bean in order for Spring to autowire it (inject dependencies).
#Component
class WorkingGroup(
override val name: String = "",
override val desc: String = ""
) : Csv() {
fun isCompatible(ct2: WorkingGroup): Boolean = this == ct2
companion object : ICsvEnumCompanion<WorkingGroup> {
private lateinit var s: S
override val VALUES: List<WorkingGroup>
by lazy {
val details = s.user().company.details ?: CompanyDetails()
details.workingGroups.map { WorkingGroup(it.name, it.desc) }
}
}
#Autowired
fun setS(value: S) {
s = value;
}
}

Resources