Blogs

Spring Data JPA and Scala… seriously?

Category
Software development
Spring Data JPA and Scala… seriously?

The combination of Scala and Spring Framework – you might say it’s not so common. While still working with both, having a few Scala projects in my track record, and having over 15 years of experience in using Spring in projects, I can testify that it’s not such a bad mixture.

For that reason, I have decided to write this blog post and to show how to combine Spring Data JPA and Scala. The aim is to use the powerful Spring Data JPA library for creating a model definition and respective repositories, yet still to continue to draw benefits from all the tricks and treats that Scala brings to the game.

Initializing the project

We will use Spring Initializr to initialize our project. You can select your favorite project setup (unfortunately you can’t select Scala as your language – selecting Java and tweaking the project in the next section of setup will do the job). 

For the sake of this example I have selected Gradle, Java, newest Spring Boot (fresh 2.5.0) and added Spring JPA Data as a dependency.

Download the project ZIP file, unzip it, init git if you like and we are ready for the next step.

Introduce Scala to your Spring Boot project

Of course, we want to have a Scala project so we will be tweaking our build.gradle file. 

What we want to add is add Scala and ScalaTest gradle plugins:

buildscript {
  dependencies {
     classpath "org.scala-lang:scala-library:$scalaLibraryVersion"
     classpath "gradle.plugin.com.github.maiflai:gradle-scalatest:$scalaTestPluginVersion"
  }
}
plugins {
  id 'org.springframework.boot' version "$springBootVersion"
  id 'io.spring.dependency-management' version '1.0.11.RELEASE'
  id 'java'
  id 'scala'
  id 'com.github.maiflai.scalatest' version "$scalaTestPluginVersion"
}

Next, we want to use all the Scala, ScalaTest and ScalaTestPlus dependencies. ScalaTest Plus dependency provides us integration between other libraries in our case we use JUnit and Mockito which we added there as dependency. We remove transitive JUnit Jupiter dependency from the Spring Test starter not to have it on our classpath without a reason.

dependencies {
  implementation "org.scala-lang:scala-library:$scalaLibraryVersion"
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  implementation "com.h2database:h2"
 
  testImplementation ('org.springframework.boot:spring-boot-starter-test') {
     exclude group: 'org.junit.jupiter', module: 'junit-jupiter'
  }
  testImplementation "junit:junit"
  testImplementation "org.mockito:mockito-core"
  testImplementation "org.scalacheck:scalacheck_$scalaVersion:$scalacheckVersion"
  testImplementation "org.scalatest:scalatest_$scalaVersion:$scalatestVersion"
  testImplementation "org.scalatestplus:scalatestplus-mockito_$scalaVersion:$mockitoScalaVersion"
  testImplementation "org.scalatestplus:junit-4-13_$scalaVersion:$scalatestJUnitVersion"
 
  testImplementation "com.h2database:h2"
 
  testRuntimeOnly "com.vladsch.flexmark:flexmark-profile-pegdown:$flexmarkVersion"
}

After we did this we are ready to change our generated Spring Boot Application from Java code to Scala as follows:

@SpringBootApplication
class ScalaSpringJpaApplication
 
object ScalaSpringJpaApplication extends App {
 SpringApplication.run(classOf[ScalaSpringJpaApplication], args: _*)
}

A simple application runner. Now, we are really ready to think of a simple model and few repositories.

Build up a model

We will build a simplified model for a Metering Service. Our service has Meters which have a name and a label. Meters can emit different Alert or Notifications. Alert has its label and severity, while Notification has again a label and a value.

For our model we create BaseEntity abstract class, containing the UUID, which is used to extend Meter class and BaseEvent abstract class. BaseEvent class, containing reference to Meter, is used as parent for both Alert and Notification classes.

Model abstract classes are annotated with @MappedSupperclass, and model classes are of course annotated with @Entity.

Following are two code snippets, for two mapped super classes BaseEntity and BaseEvent.

@MappedSuperclass
abstract class BaseEntity extends Serializable {
 @Id
 @GeneratedValue
 @Column(name = "id", length = 36)
 @Type(`type` = "uuid-char")
 var id: UUID = _
}
@MappedSuperclass
abstract class BaseEvent extends BaseEntity {
 @Type(`type` = "uuid-char")
 @JoinColumn(name = "meter_id")
 @ManyToOne(fetch = FetchType.LAZY)
 var meter: Meter = _
}

Since our entity classes have to have a default constructor we are adding a companion object to each of the classes to define helper constructors for instantiating objects (from a Service layer for example).

The code snippet below shows an example of Meter domain entity implementation.

@Entity
@Table(name = "meter")
class Meter extends BaseEntity {
 @Column(name = "name", unique = true, nullable = false)
 var name: String = _
 
 @Column(name = "label")
 var label: String = _
}
 
object Meter {
 def apply(name: String, label: Option[String]): Meter = {
   val meter = new Meter
   meter.name = name
   meter.label = label.orNull
   meter
 }
 
 def apply(id: UUID, name: String, label: Option[String]): Meter = {
   val meter = new Meter
   meter.id = id
   meter.name = name
   meter.label = label.orNull
   meter
 }
}

Check out the repository for the Alert and Notification model definition.

Create repositories

For repositories, we create a root interface for each of the model classes containing simple prototypes on what we want to do on our model. For example, we show here MeterRepository trait:

trait MeterRepository {
  def add(meter: Meter): Meter
  def update(meter: Meter): Meter
  def list(pageable: Pageable): Page[Meter]
  def get(name: String): Option[Meter]
  def remove(meter: Meter): Unit
  def removeByName(meterName: String): Unit
  def filterByNameOrLabel(pattern: String, pageable: Pageable): Page[Meter]
}

Notice we use scala types for Option, or we would use scala.List in our trait if we had functions returning a list of objects. That is the main reason why we want to have separate trait that will be used by our code.

For the implementation we define another trait which will then extend our MeterRepository and Spring JPA Data JpaRepository. We override MeterRepository methods and use creation of either JPQL query or method names to create protected methods used with the implementation.

Our implementation also transforms, in our example Optional to Option, using scala implicit transformations to go from Java to pure Scala.  

@Transactional(propagation = Propagation.MANDATORY)
trait MeterRepositoryImpl extends MeterRepository
  with JpaRepository[Meter, UUID] {
  override def add(meter: Meter): Meter = save(meter)
  override def update(meter: Meter): Meter = save(meter)
  override def list(pageable: Pageable): Page[Meter] = findAll(pageable)
  override def remove(meter: Meter): Unit = delete(meter)
  override def removeByName(meterName: String): Unit = deleteByName(meterName)
  override def get(name: String): Option[Meter] = findByName(name).toScala
  override def filterByNameOrLabel(pattern: String, pageable: Pageable): Page[Meter] = findByNameOrLabel(pattern, pageable)
 
  protected def findByName(name: String): Optional[Meter]
  protected def deleteByName(name: String): Unit
 @Query("select m from Meter m where m.name like %:pattern% or m.label like %:pattern%")
  protected def findByNameOrLabel(@Param("pattern") pattern: String, pageable: Pageable): 
  Page[Meter]
}

Checkout the rest of the implementation on the Github repository.

To be continued.

In this blog, we described how simple it is to set up the Scala project that uses Spring Data JPA. You might have noticed that we also introduced all the dependencies for the ScalaTests, that is because in the next blog we will talk more about the practice of writing integration tests for our repository implementations.

Next

Blog

How to use Vuex

Company

Our people really love it here

How it all started

Est. in 2014., gathering eight employees with eyes set on the future. No matter how set they were, they couldn’t predict the success and extent of growth that would ensue. Today there are more than 100 of us, and people are here to stay.

Stability in unstable times

The turmoil of 2020 caused great inconvenience for people all over the world. However, this did not affect our business. Quite the opposite — we not only kept all jobs and salaries intact, but we also grew in size. And we keep expanding. 

Contact

We’d love to hear from you