+ In this tutorial, you’ll learn how to use Exposed’s Domain-Specific Language (DSL) API to store and retrieve + data in a relational database by building a simple console application. +
++ By the end of this tutorial, you’ll be able to do the following: +
++ Before starting this tutorial, ensure that you have the following installed on your machine: +
++ We recommend that you install + + IntelliJ IDEA Ultimate + + which comes with built-in + + database tools + + and the + + Exposed plugin + + for code completion and inspections. However, you can use another IDE of your choice. +
+ First, you will need a basic Kotlin project setup to build upon. You can
+
In a terminal window, navigate to the destination where you want to create your project and run + the following commands to create a new folder and change directory into it: +
+Run the gradle init
task to initialize a new Gradle project:
+ When prompted, select the following options: +
+1: Application
project type.2: Kotlin
implementation language.+ For the other questions, press enter to use the default values. + The output will look like the following: +
++ Once the project has been initialized, open the project folder in your IDE. + To open the project in IntelliJ IDEA, use the following command: +
++ Before you start using Exposed, you need to provide dependencies to your project. +
+
+ Navigate to the
+
exposed-core
module provides the foundational components and abstractions
+ needed to work with databases in a type-safe manner and includes the DSL API.
+ exposed-jdbc
module is an extension of the exposed-core
module
+ that adds support for Java Database Connectivity (JDBC).
+
+ Navigate to the
+ dependencies
block:
+
+ Every database access using Exposed is started by obtaining a connection and creating a transaction.
+ To configure the database connection, use the Database.connect()
function.
+
+ Navigate to
+
+ Replace the contents of the
+
+ The Database.connect()
function creates an instance of a class that represents
+ the database and takes two or more parameters. In this case, the connection URL and the driver.
+
jdbc:h2:mem:test
is the database URL to connect to:
+ jdbc
specifies that this is a JDBC connection.
+ h2
indicates that the database is an H2 database.
+ mem
specifies that the database is in-memory, meaning the data will only
+ exist in memory and will be lost when the application stops.
+ test
is the name of the database.
+ org.h2.Driver
specifies the H2 JDBC driver to be used for establishing the connection.
+ Database.connect()
only configures the connection settings,
+ but it does not immediately establish a connection with the database. The actual connection
+ to the database will be established later when a database operation is performed.
+ + With this, you've added Exposed to your Kotlin project and configured a database connection. + You're now ready to define your data model and engage with the database using Exposed's DSL API. +
++ In Exposed, a database table is represented by an object inherited from the Table class. + To define the table object, follow the steps below. +
+Open
+ In the Table
constructor, passing the name tasks
configures a custom
+ name for the table. Keep in mind that if no custom name is specified, Exposed will generate
+ one from the class name, which might lead to unexpected results.
+
Within the Tasks
object, four columns are defined:
id
of type Int
is defined with the integer()
method.
+ The autoIncrement()
function indicates that this column will be
+ an auto-incrementing integer, typically used for primary keys.
+ title
and description
of type String
are defined
+ with the varchar()
method.
+ isCompleted
of type Boolean
is defined with the
+ bool()
method. Using the default()
function, you configure the
+ default value to false
.
+
+ At this point, you have defined a table with columns, which essentially creates the
+ blueprint for the Tasks
table.
+
+ To now create and populate the table within the database, you need to open a transaction. +
++ With Exposed’s DSL API, you can interact with a database using a type-safe syntax similar to SQL. + Before you start executing database operations, you must open a transaction. +
+
+ A transaction is represented by an instance of the Transaction
class, within which you
+ can define and manipulate data using its lambda function. Exposed will automatically manage the opening
+ and closing of the transaction in the background, ensuring seamless operation.
+
+ Navigate back to the
+
Let's break down the code and go over each section.
+
+ First, you create the tasks table using the create()
method from SchemaUtils
.
+ The SchemaUtils
object holds utility methods for creating, altering, and
+ dropping database objects.
+
+ Once the table has been created, you use the Table
extension method insert()
+ to add two new Task records.
+ Within the insert
block, you set the values for
+ each column by using the it
parameter. Exposed will translate the functions
+ into the following SQL queries:
+
+ Because the insert()
function returns an InsertStatement
, by using
+ the get()
method after the insert
operation you retrieve the
+ autoincremented id
value of the newly added row.
+
+ With the select()
extension function you then create a query to count the number
+ of rows and to retrieve the isCompleted
value for each row in the table.
+
+ Using groupBy()
groups the results of the query by the isCompleted
+ column, which means it will aggregate the rows based on whether they are completed or not.
+ The expected SQL query looks like this:
+
+ It is important to note that the query will not be executed until you call a function that
+ iterates through the result, such as forEach()
. In this example, for each group
+ we print out the isCompleted
status and the corresponding count of tasks.
+
+ Before you test the code, it would be handy to be able to inspect the SQL statements + and queries Exposed sends to the database. For this, you need to add a logger. +
++ At the beginning of your transaction block, add the following line to enable SQL query logging: +
+
+ The application will start in the
Let’s extend the app’s functionality by updating and deleting the same task.
+In the same transaction()
function, add the following code to your implementation:
Here's the breakdown:
+
+ In the Tasks.update()
function, you specify the condition to find the task
+ with id
equal to the one of the previously inserted task. If the condition is met,
+ the isCompleted
field of the found task is set to true
.
+
+ Unlike the insert()
function, update()
returns the number of updated rows.
+ To then retrieve the updated task, you use the select()
function
+ with the where
condition to only select the tasks with id
+ equal to taskId
.
+
+ Using the single()
extension function
+ initiates the statement and retrieves the first result found.
+
+ The deleteWhere()
function, on the other hand, deletes the task with the specified condition.
+
+ Similarly to update()
, it returns the number of rows that have been deleted.
+
+
transaction
is opened after the first one,
+ you will find that the table and its data has been lost
+ even though the app hasn't stopped. This is
+ expected behavior in H2 databases when managing connections and transactions.
+
+ To keep the database open, add ;DB_CLOSE_DELAY=-1
to the database URL:
+
+ Great job! You have now implemented a simple console application that uses Exposed to fetch and modify + task data from an in-memory database. Now that you’ve covered the basics, you are ready to + dive deep into the DSL API. +
++ Code example: + + %example_name% + +
+
+ In IntelliJ IDEA, click the rerun button () to restart the
+ application.
+
In IntelliJ IDEA, click on the run button
+ ()
+ to start the application.
In intelliJ IDEA, click on the notification Gradle icon
+ ()
+ on the right side of the editor to load Gradle changes.