Polyglot Cloud Blog

About DevOps, Kotlin, Spring Boot, AWS, Terraform and PostgreSQL

Spring Boot with Kotlin Snippets

Spring Boot has turned the already mighty Spring framework into a real feature powerhouse. The official support for Kotlin leverages this even further and allows the development of enterprise grade applications with a high level of productivity. This post is a collection of Spring Boot Kotlin code snippets I use in my projects.

Spring Boot and Kotlin

Running Code after Database Commit

Use Case

Its a common requirement to execute after a database transaction has been committed successfully, logging or writing to a message queue being good examples.

This is trivial to implement with programmatic transaction management but a bit more tricky with declarative transaction management.

The following code snippet registers a function to be executed after the transaction was committed.

Code

inline fun <T> T.afterCommit(crossinline block: T.() -> Unit): T {
    if (!TransactionSynchronizationManager.isActualTransactionActive()) {
        throw RuntimeException("No transaction active")
    }

    TransactionSynchronizationManager.registerSynchronization(
        object : TransactionSynchronization {
            override fun afterCommit() {
                block()
            }
        })

    return this
}

Example Usage

@Service
class CarService(
    @Autowired val repository: CarRepository
) {
    val logger by classLogger()

    @Transactional
    fun delete(carId: Long) =
        repository.deleteById(carId).afterCommit {
            logger.info("Deleted car id=[$carId] successfully")
        }
}

Class Logger by Delegation

Use Case

There are different ways to instantiate a logger for a given class; explicitly, via @Autowired’s Dependency Injection or via Kotlin’s lazy() Delegate. I consider the latter the most elegant.

Code

fun <R : Any> R.classLogger(): Lazy<Logger> =
    lazy { LoggerFactory.getLogger(this.javaClass) }

Example Usage

See the example above

Copy Data Transfer Object (DTO) fields

Use Case

Many Spring Boot @Service’s in my codebase provide CRUD operations for database entities. The requests are always Kotlin data classes and responses Spring Data JPA projections.

The request flow is as followed:

Spring Request Flow

read and delete operations are mostly handled by the @RestController calling the @Repository functions directly, whereas create and update often have side-effects and are implemented in the @Service.

update request properties are likely identical to the application’s @Entity model to be modified. The following code snippet copies properties by name from the request to the model.

Code

interface DataTransferObject {
    fun <T : Any> copyPropertiesTo(obj: T): T {
        copyProperties(this, obj)
        return obj
    }
}

private fun copyProperties(sourceObj: Any, targetObj: Any) =
    sourceObj::class.memberProperties.forEach { sourceField ->
        targetObj::class.memberProperties.filterIsInstance<KMutableProperty<*>>()
            .find { it.name == sourceField.name }
            ?.let { targetField ->
                targetField.setter.call(
                    targetObj,
                    sourceField.getter.call(sourceObj)
                )
            }
    }

Example Usage

@Entity
data class Car(
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null,

    @ManyToOne
    @JoinColumn(name = "manufacturer_id", nullable = false)
    var manufacturer: Manufacturer,

    @Column(nullable = false)
    var productionDate: OffsetDateTime,

    @Column(nullable = false)
    var serialNo: String
)
data class CarUpdateRequest(
    val manufacturerId: Long,
    val productionDate: OffsetDateTime
)
@Service
class CarService(
    @Autowired val repository: CarRepository,
    @Autowired val manufacturerService: ManufacturerService
) {

    @Transactional
    fun update(carId: Long, request: CarUpdateRequest) =
        repository.getOne(carId).let {
            request.copyPropertiesTo(it)
            it.manufacturer = manufacturerService.repository.getOne(
                request.manufacturerId
            )
            repository.save(it)
        }
}

Returning Projections from Services

Use Case

As shown in the previous use-case, returning Spring Data JPA projections from the service allows exposing only selected model attributes.

This is a helpful and frequent pattern in my codebase and used in almost almost every @Service implementation. The following code snippet adds projection extension functions for a given entity by using Kotlin’s delegation as mixin.

Code

interface Projectable<T : Any, P : Any> {
    val projectionFactory: SpelAwareProxyProjectionFactory
    val defaultProjectionClass: KClass<P>

    fun <P : Any> T.projection(clazz: KClass<P>) =
        projectionFactory.createProjection(clazz.java, this)

    fun projection(source: T) =
        source.defaultProjection()

    fun <P : Any> projection(clazz: KClass<P>, obj: Any) =
        projectionFactory.createProjection(clazz.java, obj)

    fun T.defaultProjection() =
        projectionFactory.createProjection(defaultProjectionClass.java, this)
}

class ProjectionMixin<T : Any, P : Any>(
    override val defaultProjectionClass: KClass<P>
) : Projectable<T, P> {
    override val projectionFactory = SpelAwareProxyProjectionFactory()
}

inline fun <T : Any, reified P : Any> projectionMixin() =
    ProjectionMixin<T, P>(P::class)

Example Usage

interface CarProjection {
    val id: Long
    val manufacturer: Manufacturer
    val productionDate: OffsetDateTime
}
@Service
class CarService(
    @Autowired val repository: CarRepository,
    @Autowired val manufacturerService: ManufacturerService
) : Projectable<Car, CarProjection> by projectionMixin() {

    @Transactional
    fun create(request: CarCreateRequest) =
        Car(
            manufacturer = manufacturerService.repository.getOne(
                request.manufacturerId
            ),
            productionDate = request.productionDate,
            serialNo = request.serialNo
        ).let {
            repository.save(it)
        }.defaultProjection()
}

Functions and derived Projection Attributes

Use Case

Spring Data JPA projections are compact and elegant. But sometimes mapping @Entity model attributes is not enough and we’d like to add synthetic projection attributes derived from the model or simply apply a conversion to an entity attribute.

This is possible by annotating the attribute getter via @get::JsonIgnore and adding an default interface function annotated with @JvmDefault.

Note:

@JvmDefault requires the Kotlin compiler argument -Xjvm-default=enable. See the JvmDefault documentation for more information.

Example Usage

interface CarProjection {
    val id: Long
    val manufacturer: Manufacturer

    @JvmDefault
    val daysSinceProduction: Long
        get() = ChronoUnit.DAYS.between(productionDate, OffsetDateTime.now())
    
    @get:JsonIgnore
    val productionDate: OffsetDateTime
}

JPA Entity lock / projection / getById

Use Case

JPA entities are manipulated using interfaces annotated @Repository and extending the Spring interface Repository. The following interfaces and functions extend the Repository implementation with features used as needed.

Lock

Sometimes the application must lock an entity to ensure exclusive access while manipulating its data; see PostgreSQL’s SELECT … FOR UPDATE for more information.

This can be achieved by annotating an repository interface function with @Lock(LockModeType.PESSIMISTIC_WRITE)

Dynamic projections and list of projections

For repositories to return dynamic JPA projections, the function must take a parameter of type Class<T> and return the projection type T. This works analogous for lists of projections.

Avoiding getOne() references

JpaRepository::getOne(ID id) might return a reference to an entity instead of an instance, which might lead to issues if accessed after the transaction is completed. This scenario can happen if used with projections returned by a REST controller. This might be a problem you never encountered and I really don’t wana encounter again….

The extension function CrudRepository<T, ID>::getById(id: Id): T solves this specific problem.

Code

interface QueryLocked<T> {
    @Lock(LockModeType.PESSIMISTIC_WRITE)
    fun getLockedById(id: Long): T

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    fun getLockedIfExistsById(id: Long): T?
}

interface QueryAsProjection {
    fun <T> findById(id: Long, clazz: Class<T>): T
    fun <T> findAllBy(clazz: Class<T>): List<T>
}

inline fun <reified T : Any, ID> CrudRepository<T, ID>.getById(id: ID): T =
    this.findById(id).orElseThrow {
        EntityNotFoundException(
            "Could not find entity ${T::class.simpleName} with id=$id"
        )
    }

Example Usage

@Repository
interface CarRepository :
    JpaRepository<Car, Long>,
    QueryAsProjection,
    QueryLocked<Car>
interface CarProjection {
    val id: Long
    val manufacturer: Manufacturer
    val productionDate: OffsetDateTime
    val serialNo: String
}

interface CarSummaryProjection {
    val id: Long
    val manufacturer: Manufacturer
}
@Service
class CarService(
    @Autowired val repository: CarRepository
) : Projectable<Car, CarProjection> by projectionMixin() {

    @Transactional
    fun productionCompleted(id: Long) =
        repository.getLockedById(id).let { car ->
            car.productionDate = OffsetDateTime.now()
        }.let {
            repository.save(it)
        }.defaultProjection()
        
    @Transactional
    fun allCarsSummary(): List<CarSummaryProjection> = 
        repository.findAllBy(CarSummaryProjection::class)
        
    @Transactional
    fun byId(id: Long) =
        repository.findById(id)
}

Conclusion

Hopefully some of the code snippets are useful to other developers. I’ll keep adding to this list and write dedicated posts for larger features. Drop me an email if you have any questions or feedback.


Share