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

Evolution of expertise

The agency was founded in 2014 by seasoned industry veterans with experience from large enterprises. A group of Java engineers then evolved into a full-service company that builds complex web and mobile applications. 

Flexibility is our strong suit — both large enterprises and startups use our services. We are now widely recognized as the leading regional experts in the Java platform and Agile approach.

We make big things happen and we are proud of that.

Personal development

If you join our ranks you’ll be able hone your coding skills on relevant projects for international clients.

Our diverse client portfolio enables our engineers to work on complex long term projects like core technologies for Delivery Hero, Blockchain and NFTs for Fantasy Football or cockpit interface for giants like Strabag. 

Constant education and knowledge sharing are part of our company culture. Highly experienced mentors will help you out and provide real-time feedback and support.

Contact

We’d love to hear from you