使用GORM进行数据持久化

Grails 里最让人着迷的恐怕就是 GORM 了。GORM 将数据库相关工作简化到和声明要持久化的实体一样容易。例如,代码清单6-1演示了阅读列表里的 Book 该如何用 Groovy 写成 GORM 实体。

代码清单6-1 GORM Book实体
package demo

import grails.persistence.*

@Entity
class Book { // 这是一个GORM实体

  Reader reader
  String isbn
  String title
  String author
  String description

}

就和 Book 的 Java 版本一样,这个类里有很多描述图书的属性。但又与 Java 版本不一样,这里没有分号、public 或 private 修饰符、setter 和 getter 方法或其他 Java 中常见的代码噪声。是 Grails 的 @Entity 注解让这个类变成了 GORM 实例。这个简单的实体可干了不少事,包括将对象映射到数据库,为 Book 添加持久化方法,通过这些方法可以存取图书。

要在 Spring Boot 项目里使用 GORM,必须在项目里添加 GORM 依赖。在 Maven 中,<dependency> 看起来是这样的:

<dependency>
  <groupId>org.grails</groupId>
  <artifactId>gorm-hibernate4-spring-boot</artifactId>
  <version>1.1.0.RELEASE</version>
</dependency>

一样的依赖,在 Gradle 里是这样的:

compile("org.grails:gorm-hibernate4-spring-boot:1.1.0.RELEASE")

这个库自带了一些 Spring Boot 自动配置,会自动配置所有支持 GORM 所需的 Bean。你只管写代码就好了。

GORM 在 Spring Boot 里的另一个选择

正如其名,gorm-hibernate4-spring-boot 是通过 Hibernate 开启 GORM 数据持久化的。对很多项目而言,这很好。但如果你想用 MongoDB,那你会对 Spring Boot 里的 MongoDB GORM 支持很感兴趣。

它的 Maven 依赖是这样的:

<dependency>
    <groupId>org.grails</groupId>
    <artifactId>gorm-mongodb-spring-boot</artifactId>
    <version>1.1.0.RELEASE</version>
</dependency>

下面是相同的 Gradle 依赖:

compile("org.grails:gorm-mongodb-spring-boot:1.1.0.RELEASE")

GORM 的工作原理要求实体类必须用 Groovy 来编写。我们已经在代码清单6-1里写了一个 Book 实体,下面再写一个 Reader 实体,如代码清单6-2所示。

代码清单6-2 GORM Reader实体
package demo

import grails.persistence.*

import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.userdetails.UserDetails

@Entity // 这是一个实体
class Reader implements UserDetails {

  String username
  String fullname
  String password

  Collection<? extends GrantedAuthority> getAuthorities() {
    Arrays.asList(new SimpleGrantedAuthority("ROLE_READER"))
  }

  boolean isAccountNonExpired() { // 实现了UserDetails
    true
  }

  boolean isAccountNonLocked() {
    true
  }

  boolean isCredentialsNonExpired() {
    true
  }

  boolean isEnabled() {
    true
  }

}

现在,我们的阅读列表应用程序里有了两个 GORM 实体,我们需要重写剩下的应用程序来使用这两个实体。因为使用 Groovy 是如此令人愉悦(和 Grails 十分相似),所以其他类我们也会用 Groovy 来编写。

首先是 ReadingListController,如代码清单6-3所示。

代码清单6-3 Groovy的ReadingListController
package demo;

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Controller
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.ResponseStatus

@Controller
@RequestMapping("/")
@ConfigurationProperties("amazon")
class ReadingListController {

  @Autowired
  AmazonConfig amazonConfig

  @ExceptionHandler(value=RuntimeException.class)
  @ResponseStatus(value=HttpStatus.BANDWIDTH_LIMIT_EXCEEDED)
  def error() {
    "error"
  }

  @RequestMapping(method=RequestMethod.GET)
  def readersBooks(Reader reader, Model model) {
    // 查找读者的全部图书
    List<Book> readingList = Book.findAllByReader(reader)
    model.addAttribute("reader", reader)

    if (readingList) {
      model.addAttribute("books", readingList)
      model.addAttribute("amazonID", amazonConfig.getAssociateId())
    }
    "readingList"
  }

  @RequestMapping(method=RequestMethod.POST)
  def addToReadingList(Reader reader, Book book) {

    println("SAVING: " + book)

    Book.withTransaction {
      book.setReader(reader)
      book.save() // 保存一本书
    }
    "redirect:/"
  }

}

这个版本的 ReadingListController 和第 3 章里的相比,最明显的不同之处在于,它是用 Groovy 写的,没有 Java 的那些代码噪声。最重要的不同之处在于,无需再注入 ReadingList-Repository,它直接通过 Book 类型持久化。

在 readersBooks() 方法里,它调用了 Book 的 findAllByReader() 静态方法,传入了指定的读者信息。虽然代码清单6-1没有提供 findAllByReader() 方法,但这段代码仍然可以执行,因为 GORM 会为我们实现这个方法。

与之类似,addToReadingList() 方法使用了静态方法 withTransaction() 和实例方法 save()。这两个方法也是 GORM 提供的,用于将 Book 保存到数据库里。

我们所要做的就是声明一些属性,在 Book 上添加 @Entity 注解。如果你问我怎么看——我觉得这笔买卖很划算。

SecurityConfig 也要做类似的修改,通过 GORM 而非 ReadingListRepository 来获取 Reader。代码清单6-4就是新的 SecurityConfig。

代码清单6-4 Groovy版本的SecurityConfig
package demo;

import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.core.userdetails.UserDetailsService

@Configuration
class SecurityConfig extends WebSecurityConfigurerAdapter {

  void configure(HttpSecurity http) throws Exception {
    http
      .authorizeRequests()
        .antMatchers("/").access("hasRole('READER')")
        .antMatchers("/**").permitAll()
      .and()
      .formLogin()
        .loginPage("/login")
        .failureUrl("/login?error=true")
  }

  void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
      .userDetailsService(
        { username -> Reader.findByUsername(username) }
        as UserDetailsService)
  }

}

除了用 Groovy 重写,SecurityConfig 里最明显的变化无疑就是第二个 configure() 方法。如你所见,它使用了一个闭包(UserDetailsService 的实现类),其中调用静态方法 findByUsername() 来查找 Reader,这个功能是 GORM 提供的。

你也许会好奇——在这个 GORM 版本的应用程序里,ReadingListRepository 变成什么了?GORM 替我们处理了所有的持久化工作,这里已经不再需要 ReadingListRepository 了,它的实现也都不需要了。我想你会同意代码越少越好这个观点。

应用程序中剩余的代码也应该用 Groovy 重写,这样才能和我们的变更相匹配。但它们和 GORM 没什么关系,也不在本章的讨论范围内。如果想要完整的代码,可以到示范代码页面里去下载。

此刻,你可以通过各种运行 Spring Boot 应用程序的方法来启动阅读列表应用程序。启动后,应用程序应该能像从前一样工作。只有你我知道持久化机制已经被改变了。

除了 GORM, Grails 应用程序通常还会用 Groovy Server Pages 将模型数据以 HTML 的方式呈现给浏览器。6.2节应用程序的 Grails 化还会继续。我们会把 Thymeleaf 替换为等价的 GSP。