Build a Spring Boot application with GORM
Learn how to build a Spring Boot application using GORM
Authors: Ben Rhine, Sergio del Amo
Grails Version: N/A - Spring Boot Version: 2.2.2.RELEASE
1 Grails Training
Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.
2 Getting Started
In this guide you are going to build a Spring Boot application using GORM.
2.1 What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
2.2 How to complete the guide
To get started do the following:
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/grails-guides/gorm-without-grails.git
The Grails guides repositories contain two folders:
-
initial
Initial project. Often a simple Grails app with some additional code to give you a head-start. -
complete
A completed example. It is the result of working through the steps presented by the guide and applying those changes to theinitial
folder.
To complete the guide, go to the initial
folder
-
cd
intograils-guides/gorm-without-grails/initial
and follow the instructions in the next sections.
You can go right to the completed example if you cd into grails-guides/gorm-without-grails/complete
|
If you want to start from scratch, create a new Spring Boot application using Spring Initializr.
Select a Gradle Project
using Groovy
with Spring Boot 1.5.6
. Once you have selected
your build and project type, set the Group to your organization ie. com.example
in our case
we will use demo
.
Once that is done add the Web
and h2
dependencies to the project.
Click Generate Project
.
3 Configuring your Build
Edit build.gradle files to include GORM dependencies.
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'groovy'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url "https://repo.grails.org/grails/core" } (1)
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.codehaus.groovy:groovy'
(2)
implementation("org.grails:gorm-hibernate5-spring-boot:7.0.1.RELEASE")
implementation "org.hibernate:hibernate-core"
implementation "org.hibernate:hibernate-ehcache"
(3)
runtime "org.apache.tomcat:tomcat-jdbc:8.5.0"
runtime "org.apache.tomcat.embed:tomcat-embed-logging-log4j:8.5.0"
runtimeOnly 'com.h2database:h2'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation "org.springframework.boot:spring-boot-starter-webflux" (4)
}
test {
useJUnitPlatform()
}
1 | Repository to resolve GORM dependencies. |
2 | Add the required dependencies for GORM to our project. |
3 | For connection pooling |
4 | To use WebClient in the tests, include the spring-webflux module in your project. |
3.1 Configuration
GORM for Hibernate can be configured with src/main/resources/application.yml
file when using Spring Boot.
hibernate:
hbm2ddl:
auto: update
dialect: org.hibernate.dialect.H2Dialect
spring:
datasource:
driverClassName: org.h2.Driver
url: jdbc:h2:mem:devDb
username: sa
password: ""
4 Writing the Application
We are going to create a package structure similar to the one you find in Grails application:
-
demo.controller
-
demo.domain
-
demo.service
-
demo.init
4.1 Creating the domain
Now with our packages in place lets go ahead and create our domain objects.
-
Manufacturer.groovy
-
Vehicle.groovy
Feel free to use your favorite IDE to create these or execute the following
$ cd complete/src/main/groovy/demo/domain/
$ touch Manufacturer.groovy
$ touch Vehicle.groovy
Now that all our class stubs are in place lets go ahead and edit them.
package demo.domain
import grails.gorm.annotation.Entity
import groovy.transform.ToString
import org.grails.datastore.gorm.GormEntity
@ToString
@Entity
class Manufacturer implements GormEntity<Manufacturer> {
String name
static hasMany = [vehicles: Vehicle]
static constraints = {
name blank: false
}
}
package demo.domain
import grails.gorm.annotation.Entity
import org.grails.datastore.gorm.GormEntity
import groovy.transform.ToString
@ToString
@Entity
class Vehicle implements GormEntity<Vehicle> {
String name
Integer year
static belongsTo = [manufacturer: Manufacturer]
static constraints = {
name nullable: false, blank: false
}
}
Manufacturer
and Vehicle
have a one-to-many relationship.
We are using GORM outside of Grails. Because of that, we need to annotate our domain classes with the grails.gorm.annotation.Entity
. Additionally we implement the GormEntity
trait.
It is merely to aid IDE support of GORM outside of Grails.
4.2 Seed Data
When the application starts we save some data.
package demo
import org.springframework.boot.ApplicationArguments
import demo.init.BootStrap
import groovy.transform.CompileStatic
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
@CompileStatic
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class) (1)
class Application implements ApplicationRunner {
@Autowired
BootStrap bootStrap
static void main(String[] args) {
SpringApplication.run Application, args
}
void run(ApplicationArguments args) throws Exception {
bootStrap.init()
}
}
1 | Disable Auto-configuration for Hibernate JPA. |
package demo.init
import demo.domain.Manufacturer
import demo.domain.Vehicle
import demo.service.ManufacturerService
import grails.gorm.transactions.Transactional
import groovy.transform.CompileStatic
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
@Component
@CompileStatic
class BootStrap {
@Autowired
ManufacturerService manufacturerService
@Transactional
void init() {
Manufacturer audi = new Manufacturer(name: 'audi')
audi.addToVehicles(new Vehicle(name: 'A3', year: 1996))
audi.addToVehicles(new Vehicle(name: 'A4', year: 1994))
manufacturerService.save(audi)
Manufacturer ford = new Manufacturer(name: 'ford')
ford.addToVehicles(new Vehicle(name: 'Ford KA', year: 1996))
manufacturerService.save(ford)
}
}
4.3 Creating the Service layer
Next lets create our service layer for our application.
$ cd src/main/groovy/demo/service
$ touch ManufacturerService
$ touch VehicleService
We are going to use GORM Data Services.
Data Services take the work out of implemented service layer logic by adding the ability to automatically implement abstract classes or interfaces using GORM logic.
package demo.service
import demo.domain.Manufacturer
import grails.gorm.transactions.ReadOnly
import grails.gorm.transactions.Transactional
import groovy.transform.CompileStatic
import org.springframework.stereotype.Service
@CompileStatic
@grails.gorm.services.Service(Manufacturer)
@Service
interface ManufacturerService {
List<Manufacturer> findAll()
Manufacturer save(Manufacturer manufacturer)
}
package demo.service
import demo.domain.Manufacturer
import demo.domain.Vehicle
import grails.gorm.services.Where
import groovy.transform.CompileStatic
import org.springframework.stereotype.Service
@CompileStatic
@grails.gorm.services.Service(Vehicle)
@Service
interface VehicleService {
@Where({ manufacturer.name == manufacturerName })
List<Vehicle> findAllByManufacturer(String manufacturerName)
}
4.4 Creating a controller
Finally lets create some controllers so we can have basic UI access to our data.
$ cd src/main/groovy/demo/controller
$ touch ManufacturerController
$ touch VehicleController
Now let’s edit our controllers under src/main/groovy/demo/controller
.
The controllers will have the following annotations
-
@RestController
- Denotes the controller as restful and that it can return data via url -
@Autowired
- Allows use to use dependency injection to access our services -
@RequestMapping("/")
- Sets the url mapping for the method
package demo.controller
import demo.service.VehicleService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class VehicleController {
@Autowired VehicleService vehicleService
@RequestMapping("/{manufacturerName}/vehicles")
List<String> vehiclesByManufacturer(@PathVariable String manufacturerName) {
vehicleService.findAllByManufacturer(manufacturerName)*.name
}
}
Add a test:
package demo.controller
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class VehicleControllerTest {
@Autowired
WebTestClient webClient
@Test
void fetchAudiVehicles() {
this.webClient.get().uri("/audi/vehicles").exchange().expectBody(String).isEqualTo('["A3","A4"]')
}
}
package demo.controller
import demo.service.ManufacturerService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
@RestController
class ManufacturerController {
@Autowired
ManufacturerService manufacturerService
@RequestMapping("/")
List<String> index(){
manufacturerService.findAll()*.name
}
}
Add a test:
package demo.controller
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.test.web.reactive.server.WebTestClient
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ManufacturerControllerTest {
@Autowired
WebTestClient webClient
@Test
void fetchManufacturer() {
this.webClient.get().uri("/").exchange().expectBody(String).isEqualTo('["audi","ford"]')
}
}
Run the app:
./gradlew bootRun
You should be able to call the endpoints:
curl "http://localhost:8080"
and get the response:
["audi","ford"]
Or retrieve the vehicles of a manufacturer:
curl "http://localhost:8080/audi/vehicles"
and get the response:
["A3","A4"]