Part 3: Spring Boot Components: Controller, Service, Persistence/DAO

Erwin Alberto
5 min readJan 22, 2018

--

Controller

To provide access to the application, I am going to exposed a number of REST endpoints. To do this, I wrote the MainController. I am also going to use a more specialized version of the Controller called RestController.

package com.erwindev.openpayment.controller

import com.erwindev.openpayment.domain.OpenPayment
import com.erwindev.openpayment.service.OpenPaymentService
import com.erwindev.openpayment.util.ApplicationSettings
import io.swagger.annotations.Api
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.*


/**
* Created by erwinalberto on 1/5/18.
*/

@RestController
@Api(value = "openpayment", description = "OpenPayment API")
@RequestMapping(value="/api/openpayment/v1")
class MainController{

@Autowired
lateinit var applicationSettings: ApplicationSettings

@Autowired
lateinit var openPaymentService: OpenPaymentService

@GetMapping("/payments")
fun payments(): List<OpenPayment> {
var payments: List<OpenPayment> = openPaymentService.findAllOpenPayments()
return payments
}

@GetMapping("/provider-payment/{providerId}")
fun providerPayments(@PathVariable(value="providerId") providerId: String): List<OpenPayment>{
return openPaymentService.findProviderOpenPayments(providerId)
}

@GetMapping("/payer-payment/{payerId}")
fun payerPayments(@PathVariable(value="payerId") payerId: String): List<OpenPayment>{
return openPaymentService.findPayerOpenPayments(payerId)
}
}

There are a number of things to note in this class.

  • The @RestController annotation tells the Spring Boot Application that HTTP requests are handled by this class.
  • @Api annotation is used by Swagger. I will talk about this later when I talked about how to setup Swagger in Spring Boot.
  • @RequestMapping defined in the class level maps a specific request path or pattern to a controller. In this case, every requests to the REST endpoints will be prepended with “/api/openpayment/v1”.
  • @GetMapping(“/payments”)exposes a Get Service (/api/openpayment/v1/payments) to return all the payment data. This annotation is also mapped to payments() function.
  • @GetMapping(“/provider-payment/{providerId}”)exposes a Get Service to return payment data for a specific provider. It is mapped to a providerPayments() function.
  • @PathVariable(value=”providerId”)is the value of the providerId from the uri.
  • Because I annotated the controller with @RestController, the application will render JSON by default.

DAO

Before I can do anything with the database, I have to configure the datasource so that Spring can connect to the database. In this case, I chose an embeddable database, H2.

package com.erwindev.openpayment.config

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
import javax.sql.DataSource

/**
* Created by erwinalberto on 1/6/18.
*/
@Configuration
class DatabaseConfig {

@Value("\${db.schema_file}") lateinit var schemaSql: String

@Bean(name = arrayOf("dataSource"))
fun dataSource(): DataSource {
//This will create a new embedded database and run the schema.sql script
return EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript(schemaSql)
.build()
}

@Bean
fun jdbcTemplate(@Qualifier("dataSource") dataSource: DataSource): JdbcTemplate {
return JdbcTemplate(dataSource)
}
}

There are a couple of things to note in this class.

  • The first bean, dataSource, returns a EmbeddedDatabaseBuilder object that creates an instantiated embedded H2 database. The database is instantiated with a schema script (db_schema.sql found in src/main/resources directory).
  • The second bean is the jdbcTemplate object. This bean definition is injected to the repository class.
  • By default, Spring Boot uses Tomcat JDBC connection pooling. However, I decided that I wanted to use Hikari for connection pooling. To do this, I excluded Tomcat JDBC connection pooling (exclude module: ‘tomcat-jdbc’) in the build.gradle and included included Hikari CP(compile “com.zaxxer:HikariCP:2.6.3”) instead.

To perform CRUD operations in the database, I created a DAO class, OpenPaymentDao.

package com.erwindev.openpayment.dao

import com.erwindev.openpayment.domain.OpenPayment
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.RowMapper
import org.springframework.stereotype.Repository
import java.sql.ResultSet
import java.sql.SQLException

/**
* Created by erwinalberto on 1/6/18.
*/
@Repository
class OpenPaymentDao {

@Autowired
lateinit var jdbcTemplate: JdbcTemplate

fun insert(openPayment: OpenPayment) = jdbcTemplate.update(
"""INSERT INTO open_payment (
provider_id,
provider_name,
payment_amount,
payer_id,
payer_name) VALUES (?, ?, ?, ?, ?)""",
openPayment.providerId,
openPayment.providerName,
openPayment.paymentAmount,
openPayment.payerId,
openPayment.payerName)


fun findAll(): List<OpenPayment> = jdbcTemplate.query(
"""SELECT id,
provider_id,
provider_name,
payment_amount,
payer_id,
payer_name
FROM open_payment""",
{
rs: ResultSet, _ : Int ->
OpenPayment(
rs.getLong("id"),
rs.getString("provider_id"),
rs.getString("provider_name"),
rs.getBigDecimal("payment_amount"),
rs.getString("payer_id"),
rs.getString("payer_name"))
})

fun findByProviderId(providerId: String): List<OpenPayment> = jdbcTemplate.query(
"""SELECT id,
provider_id,
provider_name,
payment_amount,
payer_id,
payer_name
FROM open_payment
WHERE provider_id = ?""", arrayOf(providerId))
{
rs: ResultSet, _ : Int ->
OpenPayment(
rs.getLong("id"),
rs.getString("provider_id"),
rs.getString("provider_name"),
rs.getBigDecimal("payment_amount"),
rs.getString("payer_id"),
rs.getString("payer_name"))
}

fun findByPayerId(payerId: String): List<OpenPayment> = jdbcTemplate.query(
"""SELECT id,
provider_id,
provider_name,
payment_amount,
payer_id,
payer_name
FROM open_payment
WHERE payer_id = ?""", arrayOf(payerId))
{
rs: ResultSet, _ : Int ->
OpenPayment(
rs.getLong("id"),
rs.getString("provider_id"),
rs.getString("provider_name"),
rs.getBigDecimal("payment_amount"),
rs.getString("payer_id"),
rs.getString("payer_name"))
}

fun findById(id: Number): OpenPayment{

var openPayment : OpenPayment = jdbcTemplate.queryForObject(
"""SELECT id,
provider_id,
provider_name,
payment_amount,
payer_id,
payer_name
FROM open_payment
WHERE id = ?""", arrayOf(id), OpenPaymentMapper())

return openPayment
}

}

private class OpenPaymentMapper : RowMapper<OpenPayment> {
@Throws(SQLException::class)
override fun mapRow(rs: ResultSet, rowNum: Int): OpenPayment {
val openPayment = OpenPayment(
rs.getLong("id"),
rs.getString("provider_id"),
rs.getString("provider_name"),
rs.getBigDecimal("payment_amount"),
rs.getString("payer_id"),
rs.getString("payer_name"))
return openPayment
}
}

There are a number of things to note in this class.

  • @Repository annotations indicates that the class defines a data repository. It is meant to interact with the datasource.
  • JdbcTemplate is injected in the class through the use of @Autowired annotation. Remember that I defined the JdbcTemplate bean in the DatabaseConfig class.
  • By default, JdbcTemplate uses PreparedStatement internally. So all of the SQL statements that I used are converted to PreparedStatement.
  • To map a row with a Kotlin object, I created a OpenPaymentMapper. In that class, I am instantiating a Kotlin data class (OpenPayment) with data coming from the ResultSet object.

Service

I could have accessed the DAO from the Controller. However, as a best practice, it is much cleaner to have the Service object perform all of the access to the database. This practice refers to the Separation of Concerns pattern. As such, I wrote all of the business logic of the application including DAO access in the OpenPaymentService class.

package com.erwindev.openpayment.service

import com.erwindev.openpayment.dao.OpenPaymentDao
import com.erwindev.openpayment.domain.OpenPayment
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service

/**
* Created by erwinalberto on 1/6/18.
*/
@Service
class OpenPaymentService {
@Autowired
lateinit var openPaymentDao: OpenPaymentDao

fun addOpenPayment(openPayment: OpenPayment){
openPaymentDao.insert(openPayment)
}

fun findAllOpenPayments(): List<OpenPayment> = openPaymentDao.findAll()

fun findProviderOpenPayments(providerId: String): List<OpenPayment> = openPaymentDao.findByProviderId(providerId)

fun findPayerOpenPayments(paymentId: String): List<OpenPayment> = openPaymentDao.findByPayerId(paymentId)

fun findOpenPayment(id: Number): OpenPayment = openPaymentDao.findById(id)
}

There are a few things to note in this class.

  • @Service annotation registers this class in Spring framework so that other classes can be injected with it.
  • Other than this class indicates that it is a service class and that it should hold the business logic of the application, there’s nothing special that this annotation provides.

Next, I’ll show how to test the different components of the application.

Part 4: Writing Tests

You can find the code this GitHub repo.

--

--