Spring Boot で GORM を使う(Stand Alone Application)
Posted on July 28, 2014 at 00:43 (JST)
Spring Boot の勉強会を前に、ちょっと遊んでみました。
GrailsのGORMがStand Aloneで使えるので、DBアクセスにはGORMを使用しています。
テストを動かすところまでのサンプルです。
サンプルはGithubにて公開しています。[ spboot-gorm-standalone ]
1. Gradleの設定
[ build.groovy ]
apply plugin: 'java'
apply plugin: 'groovy'
apply plugin: 'spring-boot'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
mainClassName = "${'com.example.myproject.Application'}"
buildscript {
repositories {
mavenCentral()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.0.BUILD-SNAPSHOT")
}
}
jar {
baseName = 'myproject'
version = '0.0.1-SNAPSHOT'
}
repositories {
mavenCentral()
maven { url "http://repo.spring.io/snapshot" }
maven { url "http://repo.spring.io/milestone" }
mavenLocal()
maven { url 'http://repo.grails.org/grails/core/' }
}
def groovyVersion = '2.3.4'
def grailsVersion = '2.3.11'
def gormVersion = '1.3.7'
def h2Version = '1.3.170'
def spockVersion = '0.7-groovy-2.0'
dependencies {
compile("org.springframework.boot:spring-boot-starter",
"org.springframework.boot:spring-boot-starter-logging",
"org.codehaus.groovy:groovy-all:<span>$</span>{groovyVersion}",
"org.grails:grails-gorm:<span>$</span>{gormVersion}",
"org.grails:grails-bootstrap:<span>$</span>{grailsVersion}",
"org.grails:grails-spring:<span>$</span>{grailsVersion}",
"com.h2database:h2:<span>$</span>{h2Version}")
testCompile("org.springframework.boot:spring-boot-starter-test",
"org.spockframework:spock-core:<span>$</span>{spockVersion}")
}
2. GORMを使用するための設定
DB接続先などもこのファイルに定義します
[ SpringBeans.groovy ]
import org.apache.commons.dbcp.BasicDataSource
import org.codehaus.groovy.grails.orm.hibernate.events.PatchedDefaultFlushEventListener
import org.codehaus.groovy.grails.orm.hibernate.support.ClosureEventTriggeringInterceptor
import org.springframework.context.support.ResourceBundleMessageSource
beans {
xmlns gorm:"http://grails.org/schema/gorm"
xmlns context:"http://www.springframework.org/schema/context"
xmlns tx:"http://www.springframework.org/schema/tx"
context."annotation-config"()
tx."annotation-driven"()
messageSource(ResourceBundleMessageSource) {
basename = "messages"
}
dataSource(BasicDataSource) {
driverClassName = "org.h2.Driver"
url = "jdbc:h2:mem:test"
username = "sa"
password = ""
}
gorm.sessionFactory("data-source-ref": "dataSource",
"base-package": "com.example.myproject",
"message-source-ref": "messageSource") {
hibernateProperties = ["hibernate.hbm2ddl.auto": "create",
"hibernate.dialect": "org.hibernate.dialect.H2Dialect"]
eventListeners = ["flush": new PatchedDefaultFlushEventListener(),
"pre-load": new ClosureEventTriggeringInterceptor(),
"post-load": new ClosureEventTriggeringInterceptor(),
"save": new ClosureEventTriggeringInterceptor(),
"save-update": new ClosureEventTriggeringInterceptor(),
"post-insert": new ClosureEventTriggeringInterceptor(),
"pre-update": new ClosureEventTriggeringInterceptor(),
"post-update": new ClosureEventTriggeringInterceptor(),
"pre-delete": new ClosureEventTriggeringInterceptor(),
"post-delete": new ClosureEventTriggeringInterceptor()]
}
context."component-scan"("base-package": "com.example.myproject")
}
3. アプリケーション作成
まずはdomainクラスから。
基本的にはGrailsと同様でOKです。
[ Person.groovy ]
package com.example.myproject.domain
import grails.persistence.Entity
import org.apache.commons.lang.builder.ToStringBuilder
import org.apache.commons.lang.builder.ToStringStyle
@Entity
class Person {
String familyName
String givenName
Date dateCreated
Date lastUpdated
static constraints = {
familyName maxSize: 10, blank: false
givenName maxSize: 10, blank: false
dateCreated nullable:true
lastUpdated nullable:true
}
@Override
String toString() {
ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
※ @ToStringや@Canonicalが効かないみたいなので、ToStringBuilderを使用しています。
つづいて、Serviceクラス。
インターフェースをかませなくてもOK。
[ PersonService.groovy ]
package com.example.myproject.service
import com.example.myproject.domain.Person
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service("personService")
@Transactional
class PersonService {
private static final Logger LOG = LoggerFactory.getLogger(PersonService.class)
def findAll() {
return Person.findAll()
}
def count() {
return Person.count()
}
def save(Person person) {
LOG.debug('★ #save(Person) was called ★')
person.save()
}
def save(Collection<Person> persons) {
LOG.debug('★ #save(Collection) was called ★ count: ' + persons.size())
persons.each { person ->
person.save()
}
}
def validate(Person p) {
p.validate()
}
}
最後に、Applicationクラス。
[ Application.groovy ]
package com.example.myproject
import com.example.myproject.domain.Person
import com.example.myproject.service.PersonService
import grails.spring.BeanBuilder
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationContext
class Application {
private static final Logger LOG = LoggerFactory.getLogger(Application.class)
public static void main(String[] args) {
BeanBuilder beanBuilder = new BeanBuilder()
beanBuilder.loadBeans("classpath:SpringBeans.groovy")
ApplicationContext context = beanBuilder.createApplicationContext()
PersonService personService = context.getBean("personService") as PersonService
personService.save([
new Person(familyName: 'Yamada', givenName: 'Taro'),
new Person(familyName: 'Asakura', givenName: 'Hanako')
])
LOG.info("★persons count: <span>$</span>{personService.count()}")
LOG.info ("★persons: <span>$</span>{personService.findAll()}")
Person person = personService.findAll()[0]
person.givenName = "Koziro"
personService.save(person)
LOG.info ("★persons: <span>$</span>{personService.findAll()}")
}
}
以上で動きます。XMLにて設定を行わなくてもSpringが動かせるようになったのは嬉しいですね♪
4. テストクラス作成
フレームワークはテストのやりやすさも重要!
ということで、JUnitベースとSpockベースで作ってみました。
[ PersonServiceTest.groovy ]
package com.example.myproject
import com.example.myproject.domain.Person
import com.example.myproject.service.PersonService
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import java.text.MessageFormat
import static org.hamcrest.CoreMatchers.*
import static org.junit.Assert.assertThat
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:SpringBeans.groovy")
class PersonServiceTest {
@Autowired
PersonService sut
@Test
public void saveで登録できる() {
Person p = new Person(familyName: 'Yagyuu', givenName: 'ZyuBei')
assert !p.id
sut.save(p)
def actual = Person.findAll()[0].id
assertThat(actual, is(not(nullValue())))
}
@Test
public void 名前が11文字の場合は文字数チェックでエラーとなること() {
Person p = new Person(familyName: 'Yagyuu', givenName: '12345678901')
sut.validate(p)
def error = p.errors.fieldErrors.first()
def message = MessageFormat.format(
error.defaultMessage,
error.field,
error.rejectedValue,
10)
assertThat(message,
is('Property [givenName] with value [12345678901] exceeds the maximum size of [10]'))
}
}
エラーメッセージはmessage.propertiesを用意して、任意の値に上書きできます。
各チェックに対応するkeyは、Grailsと同様です。
なお、メッセージの合成([0]に値を埋め込む)は自分で処理を書く必要があります。
[ PersonServiceSpeck.groovy ]
package com.example.myproject
import com.example.myproject.domain.Person
import com.example.myproject.service.PersonService
import org.junit.Test
import org.junit.runner.RunWith
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import spock.lang.Specification
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:SpringBeans.groovy")
class PersonServiceSpec extends Specification {
@Autowired
PersonService personService
@Test
def "Spockでテストを行う"(){
when: "app is run without arguments"
personService.save(new Person(familyName: 'Yagyuu', givenName: 'ZyuBei'))
then:
Person.findAll().size() > 0
}
}
Spockのテストクラスはとりあえず上記で動きましたが、いろいろ微妙な気が。。。
公式ドキュメント
では
@ContextConfiguration(loader = SpringApplicationContextLoader.class)
を使用していますが、上記ではserviceがDIされない。。。
SpringのDI設定について、もっと調べる必要がありそうです。
以上です。