How I built my first Kotlin-VertX-Hibernate stack
Jan Vladimir MostertI've been using Kotlin for close to 6 months now, and have been experimenting with quite a number of frameworks trying to find the combination that will allow me the most flexibility as well as great performance.
Since I've used Spring Boot previously, and ran into a few troubles after trying to do things which the framework didn't support, I steered clear of it. Spring5
, which supports asynchronous operations, only reached Milestone 1 when I was in the market for a new framework. So, I parked it until it was stable enough for production use.
DropWizard
looked like a decent option as well, considering its use of Jersey
, Jetty
, Jackson
, and Metrics
.
JDBI
+ Liquibase
made for decent choices, to take care of the DB layer, but I needed Hibernate
for the having to switch between MySQL
, SQLServer
, and PostgreSQL
. Since a lot of my existing logging infrastructure depends on Log4j
, having to hack around to see how I can replace Logback
, did not seem like a good use of my time.
I've been sharing articles about VertX
from time to time, including spectacular benchmarks, but haven't used it myself until now. So, I wanted to see how easy it would be to write a complete system in Kotlin
+ VertX
, and how complex it would be, compared to the other alternatives I've listed above.
Maybe you can be the judge of the ease of the aforementioned setup. Let's start with a basic maven pom.xml
in the root directory ...
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hashnode</groupId>
<artifactId>demo</artifactId>
<packaging>jar</packaging>
<version>1.0.0</version>
<name>Kotlin Vertx Hibernate Demo</name>
<properties>
</properties>
<dependencies>
</dependencies>
<build>
</build>
</project>
For Kotlin support, we need the Kotlin Standard lib:
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
... and the Kotlin Maven plugin:
<build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>1.6</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>process-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>process-test-sources</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
We would be be using 1.0.5-2 for this demo:
<properties>
<kotlin.version>1.0.5-2</kotlin.version>
</properties>
For VertX, we need VertX Core, and if we need to open websockets, or enable REST endpoints, we need VertX Web.
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>${vertx.version}</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>${vertx.version}</version>
</dependency>
We would be using 3.3.3 for this demo (note that a Kotlin specific version of VertX will be released in early 2017, for now we're using VertX Java inside Kotlin)
<properties>
<vertx.version>3.3.3</vertx.version>
</properties>
And that's most of the XML boilerplate - you can always switch to gradle
if you prefer to get away from the maven
boilerplate.
Now in your src/main/java
directory (you can also use src/main/kotlin
if you prefer, but that would require us to configure the Maven Kotlin plugin, to look for source files in that directory), create a HashnodeDemo.kt
file and add the following code to the file:
import io.vertx.core.AbstractVerticle
import io.vertx.core.Vertx
import io.vertx.core.logging.LoggerFactory
import io.vertx.ext.web.Router
import java.io.IOException
object HashnodeDemo : AbstractVerticle() {
// initiate logging system
private val log = LoggerFactory.getLogger(HashnodeDemo.javaClass)
@JvmStatic
@Throws(IOException::class)
fun main(args: Array<String>) {
// setup verx
val vertx = Vertx.vertx()
val router = Router.router(vertx)
router.get("/").handler { it.response().end(" Hello World :-) ") }
// start vertx
vertx.createHttpServer().requestHandler { router.accept(it) }.listen(9090)
}
}
Now if you run this class from your IDE (I'm using IntelliJ, so for me it's right-click
and Run HashnodeDemo
), and open your browser at localhost:9090
, you should see Hello World :-)
in your browser.
So, with a single pom.xml
file, and a single Kotlin object
we've got a web server that can respond to GET
requests. Nothing fancy yet, but as you would see further on, VertX mostly gets out of your way allowing you almost full control of the response going back.
Handling WebSockets can also be done with extra minimal code, but I'll cover that in more detail in a future post:
vertx.createHttpServer().websocketHandler { ws ->
log.info("WebSocket Connected")
ws.handler {
// print content of request to logs
log.info(it.toString())
// write data back to browser
ws.writeFinalTextFrame("Hello World")
}
}.requestHandler { router.accept(it) }.listen(port)
CORS support can also be added with an extra couple of lines (this route should be added before any GET
, POST
, etc... routes). To allow cross-domain cookies, use allowCredentials
// handle CORS
router.route().handler(
CorsHandler.create(origin)
.allowCredentials(true)
.allowedMethod(HttpMethod.GET)
.allowedMethod(HttpMethod.POST)
.allowedMethod(HttpMethod.OPTIONS)
.allowedHeader("X-PINGARUNER")
.allowedHeader("Content-Type"))
VertX, just like NodeJS, uses an event-loop, and at no point should the event-loop be blocked. If you do block it, expect lots of warnings in your logs, and a poor performance. Fortunately, VertX gives you a mechanism out of the box that will allow you to handle blocking requests with ease.
To demonstrate, let's add some ASCII art that will displayed when the application starts. In src/main/resources
, create ascii.txt
and add some ASCII art to it:
_ _ _ _ _____
| | | | | | | | | __ \
| |__| | __ _ ___| |__ _ __ ___ __| | ___ | | | | ___ _ __ ___ ___
| __ |/ _` / __| '_ \| '_ \ / _ \ / _` |/ _ \ | | | |/ _ \ '_ ` _ \ / _ \
| | | | (_| \__ \ | | | | | | (_) | (_| | __/ | |__| | __/ | | | | | (_) |
|_| |_|\__,_|___/_| |_|_| |_|\___/ \__,_|\___| |_____/ \___|_| |_| |_|\___/
Starting Hashnode Demo 1.0.0 ...
Just before `//start vertx`, add the following block of code:
// show ascii art
vertx.executeBlocking<String>({
it.complete(InputStreamReader(javaClass.getResourceAsStream("/ascii.txt")).readText())
}, {
log.info(it?.result())
})
During startup, the blocking call to the file-system would run in a worker pool while the rest of VertX continues to run asynchronously — best of both worlds; handling an asynchronous request, while still being able to make use of blocking calls in the background...
Now before you follow this tutorial and add Hibernate
to your project, consider the vast array of options available. Hibernate
, in my opinion, is the most feature-complete ORM out there, but that also makes it the heaviest ORM available. Other options include:
- JDBI looks decent if you just want to write normal SQL queries, by hand, and need help in binding params and results
- VertX JDBC is very bare-bone, and will probably require lots of work on your side, depending on what you are looking for
- ReQuery comes bundled with RxJava out of the box, and feels almost like
Hibernate
, but it's much more lightweight - Falkon is another option if you like the DSL style
- JOOQ also comes highly recommended.
- Kotlin Kwery, I've had a look at it, but haven't used it yet.
To get started with Hibernate
, let's add some more dependencies in our pom.xml
:
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
<exclusions>
<exclusion>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
</exclusion>
<exclusion>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate.version}</version>
</dependency>
Let's use MySQL for this demo:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
And while we're at it, let's add a connection pool as well, to reduce latency:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${hibernate.version}</version>
</dependency>
We would be using the latest versions of the MySQL driver, and Hibernate:
<properties>
<mysql.version>6.0.5</mysql.version>
<hibernate.version>5.2.4.Final</hibernate.version>
</properties>
We would also need to setup the persistence.xml
file at src/main/resources/META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="default">
<description>Persistence XML</description>
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<!-- Hibernate Config -->
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect" />
<property name="hibernate.generate_statistics" value="false" />
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy"/>
<property name="hibernate.connection.charSet" value="UTF-8"/>
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.use_sql_comments" value="false"/>
<!-- JDBC Config -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/hashnodedb?useSSL=false" />
<property name="javax.persistence.jdbc.user" value="username" />
<property name="javax.persistence.jdbc.password" value="password" />
<!-- Connection Pool -->
<property name="hibernate.connection.provider_class"
value="org.hibernate.connection.C3P0ConnectionProvider" />
<property name="hibernate.c3p0.max_size" value="5" />
<property name="hibernate.c3p0.min_size" value="1" />
<property name="hibernate.c3p0.acquire_increment" value="1" />
<property name="hibernate.c3p0.idle_test_period" value="300" />
<property name="hibernate.c3p0.max_statements" value="0" />
<property name="hibernate.c3p0.timeout" value="100" />
</properties>
</persistence-unit>
</persistence>
The hibernate.dialect
sets the flavour of SQL you are running. We would be using MySQL
with InnoDB
for this demo. hibernate.hbm2ddl.auto
can be set to validate
, to validate the database against the current database model; update
will update your database based on your model; whereas create-drop
will recreate your database effectively every time your restart.
Set hibernate.hbm2ddl.auto
to validate
when going to production, otherwise you might just drop / corrupt your production database.
The JDBC Config
is simply where you configure the location of your database, username, and password.
The Connection Pool
section allows us to setup a connection pool, in this example we set it to use only 5 connections at maximum. If your application is the only application connecting to the database, you can set that close to the maximum number of connections your database can handle. Just remember that those connections will be kept open as long as the application is running.
Let's create a few Hibernate
models — first, let's create a base model so that we don't have to re-declare all the basic fields in all the models we create. Create a new directory src/main/java/com/hashnode/demo/model
Now, let's create the base model in StandardEntity.kt
:
package com.hashnode.demo.model
import java.util.*
import javax.persistence.*
@MappedSuperclass
open class StandardEntity {
@Id
@GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
@Column(name = "id")
var id: Int? = null
@Version
@Column(name = "version")
private var version: Int = 0
@Temporal(TemporalType.TIMESTAMP)
var created: Date = Date()
@Temporal(TemporalType.TIMESTAMP)
var modified: Date = Date()
@PreUpdate
protected fun onUpdate() {
this.modified = Date()
}
}
Notice the @MappedSuperclass
. If you want to extend a base model in Hibernate
, that base model should be annotated with @MappedSuperclass
, otherwise extending the base model won't work.
Let's create another entity in Company.kt
package com.hashnode.demo.model
import com.hashnode.demo.model.StandardEntity
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.EntityManager
import javax.persistence.Table
@Entity
@Table(name = "Companies")
class Company(
@Column(unique = true)
var name: String = ""
) : StandardEntity() {
fun getCompanyByName(em: EntityManager, name: String): Company {
val query = em.createQuery("SELECT o FROM Company AS o WHERE o.name=:name", Company::class.java)
query.setParameter("name", name)
return query.singleResult
}
}
Next we bootstrap Hibernate
in our main class, HashnodeDemo.kt
. First let's add a lateinit
variable, just after we initiate logging:
object HashnodeDemo : AbstractVerticle() {
// initiate logging system
private val log = LoggerFactory.getLogger(HashnodeDemo.javaClass)
lateinit var emf: EntityManagerFactory
lateinit
indicates that we don't want the variable to be null
, but we don't want to initialise it immediately either.
Just before we display the ASCII art, we instantiate emf
.
// setup hibernate
emf = Persistence.createEntityManagerFactory("default", hibernate)
This setup would give you a very quick startup time, but Hibernate
will only startup after the first query has been fired, meaning your first user will get a very slow response. To fix that, we make a dummy query which would force Hibernate
to start in the background.
vertx.executeBlocking<Any>({
it.complete(emf.createEntityManager().createNativeQuery("SELECT 'Hibernate Ready!'").singleResult)
}, {
log.info(it?.result())
})
Since we're using the entity manager, all queries would require an entity manager, whether you do a query inside, or outside of a transaction. Let's create a few helper functions to make that easier.
Create another package where you can place your utils, and add a Transaction.kt
file to it. Add the following code to your Transaction.kt
file:
package com.hashnode.demo.util
import io.vertx.core.logging.LoggerFactory
import javax.persistence.EntityManager
val log = LoggerFactory.getLogger(AtomApi.javaClass)
/**
* Inline wrapper function used for doing transactions
*/
inline fun transaction(f: (em: EntityManager) -> Unit) {
val em = AtomApi.emf.createEntityManager()
try {
em.transaction.begin()
f(em)
em.transaction.commit()
} catch (e: Exception){
log.error(e.message, e)
em.transaction.rollback()
} finally {
em.close()
}
}
/**
* Inline wrapper function to give access to the entity manager in codeblock
*/
inline fun notransaction(f: (em: EntityManager) -> Unit){
val em = AtomApi.emf.createEntityManager()
try {
f(em)
} catch (e: Exception){
log.error(e.message, e)
} finally {
em.close()
}
}
Now whenever you need to run a bunch of queries that should be transactional, use a transaction block:
transaction { em ->
val query = em.createQuery("SELECT o FROM Company AS o WHERE o.id=:id", Company::class.java)
query.setParameter("id", company.id)
val company = query.singleResult
company.name = "new company name"
em.merge(company);
}
And for non-transactional queries, simply use the notransaction
block in the same way.
notransaction { em ->
val query = em.createQuery("SELECT o FROM Company AS o WHERE o.id=:id", Company::class.java)
query.setParameter("id", company.id)
val company = query.singleResult
println(company.name)
}
Any new entities, which you want to create (or update), should always be in a transaction
block. This can be done in a one-liner (remember, if you don't specify the lambda-variable in Kotlin, it defaults to it
)
transaction { it.persist(Company(name = "another company")) }
Remember that JDBC
is blocking, so any Hibernate
queries should be run using vertx.executeBlocking
. This is typically how you would structure it, either pass in the ServerWebSocket
; or the Route
, if you're using the REST (GET
, POST
, etc...) methods. Execute the query inside the executeBlocking
section, and write the result back in the second lambda.
fun listCompanies(vertx: Vertx, ws: ServerWebSocket, session: Session, data: String) {
vertx.executeBlocking<List<Company>>({
notransaction { em ->
val query = em.createQuery("SELECT o FROM Company", Company::class.java)
it.complete(query.resultList)
}
}, {
if (it.succeeded()) {
ws.writeFinalTextFrame(Event("listCompanies", it.result()).toString())
}
})
}
How you structure your files, is completely up to you. You could even use extension methods to cut down on the boilerplate, but you do have the option to structure things according to how they suit you the best.
As I've shown, Kotlin
to VertX
, works like a well-fitting glove to a hand. Adding an ORM-layer is straight-forward as well. I've also demonstrated the addition of Hibernate
, which in my case was the right choice for the stack that I work on. If you don't need to jump between databases, and prefer something more lightweight, be sure to check out the other options I have mentioned above.
If you have any questions, feel free to post them here, and I'll respond when time allows.