(快速参考)

服务层

4.0.2

服务层

365bet地区365bet地区定义了服务层的概念365bet地区团队不鼓励将核心应用程序逻辑嵌入控制器内部,因为它不会促进重用和关注点的清晰分离

365bet地区365bet地区中的服务是将大多数逻辑放入应用程序中的地方,而控制器则负责通过重定向等处理请求流。

创建服务

365bet地区您可以通过运行创建服务在终端窗口中从项目根目录执行命令

grails创建服务helloworld简单
365bet地区如果未使用创建服务脚本指定软件包,365bet地区将自动使用grails defaultPackage定义于grails应用配置会议应用yml

上面的示例将在该位置创建服务grails应用程序服务helloworld SimpleService groovy服务的名称以约定结尾服务除此之外,服务是普通的Groovy类

 简单服务 {
}

声明式交易

声明式交易

服务通常与之间的协调逻辑有关域类因此,它们经常涉及跨越大型操作的持久性。鉴于服务的性质,它们经常需要事务处理行为,因此您可以将程序化事务与withTransaction但是,此方法是重复性的,不能完全利用Spring的基础事务抽象的功能

交易性

进口 

交易性
  {

}

结果是所有方法都包装在事务中,并且如果方法抛出Checked或Runtime异常或错误,则该方法将自动回滚。事务的传播级别默认设置为需要传播.

365bet地区365bet地区版本是默认情况下第一个使用GORM的版本。Checked异常不会在GORM之前回滚事务只有抛出运行时异常的方法(即扩展RuntimeException的方法才回滚事务)
警告依赖注入是个只要声明式交易的工作方式如果您使用诸如新的BookService

事务注释与事务属性

365bet地区在365bet地区之前的365bet地区版本中,365bet地区创建了Spring代理并使用了交易性的365bet地区用于启用和禁用代理创建的属性默认情况下,在使用365bet地区及更高版本创建的应用程序中,这些代理处于禁用状态,从而有利于交易性转型

365bet地区对于365bet地区 x和x版本,如果您不希望启用此功能,则必须设置春季交易管理更改或删除配置grails应用配置会议应用yml要么grails应用配置会议应用程序.

365bet地区在365bet地区 x中,用于事务管理的Spring代理已被完全删除,您必须使用365bet地区 AST转换。在365bet地区 x中,如果希望继续使用Spring代理进行事务管理,则必须使用适当的Spring配置手动配置它们。

365bet地区此外,在365bet地区之前,默认情况下服务是可交易的,从365bet地区开始,它们仅在以下情况下才是可交易的:交易性应用转换

自定义交易配置

365bet地区365bet地区还提供交易性非交易的对于需要在每个方法级别对事务进行更细粒度控制或需要指定替代传播级别的情况的注释,例如非交易的注释可用于标记使用类注释时要跳过的特定方法交易性.

使用以下注释服务方法交易性365bet地区以与添加相同的方式禁用该服务的默认365bet地区事务行为。交易错误这样做是因为如果您使用任何注释,则必须注释所有需要事务处理的方法

在这个例子中清单书使用只读事务updateBook使用默认的读写事务deleteBook

进口 grails gorm Transactions事务性

 图书服务 {

    交易性只读真正)
    定义 清单书() {
        清单交易性
    定义 updateBook() {
        // ...
    }

    定义 deleteBook() {
        // ...
    }
}

进口 grails gorm Transactions事务性

交易性
 图书服务 {

    定义 清单书() {
        清单定义 updateBook() {
        // ...
    }

    定义 deleteBook() {
        // ...
    }
}

该版本默认由于类级别的注释而将所有方法都以事务方式读写。清单书方法重写此方法以使用只读事务

进口 grails gorm Transactions事务性

交易性
 图书服务 {

    交易性只读真正)
    定义 清单书() {
        清单定义 updateBook() {
        // ...
    }

    定义 deleteBook() {
        // ...
    }
}

虽然updateBookdeleteBook

有关更多信息,请参考Spring用户指南中的使用交易.

与Spring不同,您不需要任何预先配置即可使用交易性365bet地区只需根据需要指定注释,365bet地区就会自动检测到它们

交易状态

365bet地区默认情况下在365bet地区事务服务方法中可用

进口 grails gorm Transactions事务性

交易性
 图书服务 {

    定义 deleteBooktransactionStatus setRollbackOnly

事务和多数据源

给定两个域类,例如

  {
    标题
  {
    标题静态的'图书'
    }
}

交易性要么只读注解

进口 grails gorm交易ReadOnly
进口 grails gorm Transactions事务性
进口 常规转换CompileStatic

静态编译
 图书服务 {

    只读('图书')
    清单<找到所有交易性('图书')
    标题 (标题
静态编译
 电影服务 {

    只读
    清单

事务回滚和会话

了解事务和休眠会话

使用事务时,必须考虑到Hibernate如何处理基础持久性会话的重要考虑因素。回滚事务时,将清除GORM使用的Hibernate会话。这意味着该会话中的所有对象都将分离并且未初始化访问延迟加载的集合将导致LazyInitializationException.

 作者 {
    那么整数年龄静态的有很多图书: ]
}

如果要使用以下连续事务保存两名作者,请执行以下操作

作者的交易状态作者那么: "斯蒂芬·金", 年龄: 40保存状态setRollbackOnly作者具有交易状态作者那么: "斯蒂芬·金", 年龄: 40保存

因为第一个事务回滚了作者,所以只有第二个作者会被保存。保存通过清除Hibernate会话如果未清除Hibernate会话,则两个作者实例都将保留,这将导致非常意外的结果

但是,获得一个LazyInitializationException由于会话已清除

例如,考虑以下示例

 AuthorService {

    虚空updateAge ID整型年龄定义作者作者ID获取作者年龄age如果作者isTooOld AuthorException"太老"作者
 AuthorController {

    定义authorService定义 更新年龄() {
        尝试authorService updateAge params id params int"年龄"))
        }
        抓住并渲染"作者书${电子书}"
        }
    }
}

在上面的示例中,如果交易的年龄作者年龄超过了isTooOld通过抛出一个方法AuthorExceptionAuthorException引用作者,但当图书关联被访问LazyInitializationException将被清除,因为基础的Hibernate会话已被清除

要解决此问题,您有很多选择,一种是确保您急切地查询以获取所需的数据

 AuthorService无效的updateAge ID整型年龄定义作者作者findById id:[图书:"急于"]])
        ...

在这个例子中图书检索时将查询关联作者.

这是最佳的解决方案,因为它比以下建议的解决方案需要更少的查询

另一个解决方案是在事务回滚后重定向请求

 AuthorControllerAuthorService authorService定义 更新年龄() {
        尝试authorService updateAge params id params int"年龄"))
        }
        抓住即时消息"无法更新年龄"重新导向行动:"节目", ID参数ID

在这种情况下,新的请求将处理检索作者最后,第三种解决方案是检索作者再次确保会话保持正确状态

 AuthorController {

    定义authorService定义 更新年龄() {
        尝试authorService updateAge params id params int"年龄"))
        }
        抓住定义作者作者阅读参数id渲染"作者书${作者书}"
        }
    }
}

验证错误和回滚

一个常见的用例是,如果存在验证错误,则回滚事务。例如,考虑此服务

进口 验证ValidationException

 AuthorService {

    虚空updateAge ID整型年龄定义作者作者ID获取作者年龄age如果作者验证 ValidationException"作者无效"作者错误

要重新呈现事务已回滚的相同视图,可以在呈现之前将错误与刷新的实例重新关联

进口 验证ValidationException

 AuthorController {

    定义authorService定义 更新年龄() {
        尝试authorService updateAge params id params int"年龄"))
        }
        抓住ValidationException e定义作者作者读取参数id作者错误e错误呈现视图: "编辑", 模型: [作者作者

范围服务

默认情况下,对服务方法的访问不会同步,因此不会阻止这些方法的并发执行实际上,由于该服务是单例并且可以同时使用,因此您应该非常小心地将状态存储在服务中,或者走轻松而美好的路,永远不要将状态存储在服务中

您可以通过将服务放置在特定范围中来更改此行为。

  • 原型每次将新服务注入另一个类时都会创建一个新服务

  • 请求每个请求将创建一个新服务

  • 将仅针对当前请求和下一个请求创建新服务

  • 在Web流中,该服务将在流范围内存在

  • 会话在Web流中,服务将存在于对话范围内,即根流及其子流

  • 会议为用户会话的范围创建服务

  • 单身人士默认值仅存在一个服务实例

如果您的服务是, 要么会话范围必须执行java i可序列化并且只能在Web Flow的上下文中使用

要启用其中一个作用域,请向您的类添加静态作用域属性,该属性的值是上述值之一,例如

静态的范围""
升级中

365bet地区从365bet地区开始,新应用程序的配置将默认将控制器的范围设置为单身人士如果单身人士控制器与原型范围内的服务,这些服务按照控制器单例有效地运行如果需要非单例服务,则控制器范围也应更改

看到控制器和示波器在用户指南中了解更多信息

您还可以配置服务是否被延迟初始化默认情况下,此设置为真正但是您可以禁用此功能,并使用惰性初始化属性

静态的惰性初始化

依赖注入和服务

依赖注入基础

365bet地区365bet地区服务的一个关键方面是使用能力春季框架365bet地区s的依赖项注入功能365bet地区通过约定支持依赖项注入。换句话说,您可以使用服务的类名的属性名表示将它们自动注入到控制器标记库中,依此类推。

作为示例,给定一个服务图书服务如果定义一个名为bookService在控制器中如下

 BookController {
    定义bookService

 AuthorServiceBookService bookService
注意通常情况下,属性名称是由小写的第一个字母(例如:图书服务类将映射到名为bookService.

如果类名的首字母大写,则与标准JavaBean约定保持一致,属性名与类名相同。例如,JDBCHelperService上课是JDBCHelperServicejDBCHelperService要么jdbcHelperService.

有关反大写规则的更多信息,请参见JavaBean规范部分。

由于遍历所有嵌套对象以执行注入,因此只有顶层对象会受到注入,这是性能问题

注入非默认数据源时要小心,例如使用此配置

数据源:
    数据源:
        派对: 真正
        jmxExport: 真正次要的派对: 真正
        jmxExport: 真正
        .....

您可以注入主要数据源就像你期望的那样

 BookSqlServicedef dataSource

但是要注入次要的您必须使用Spring的数据源自动接线注射或.

 BookSqlSecondaryService {

  自动接线
  预选赛('dataSource次要'def dataSource

依赖注入和服务

您可以使用相同的技术将服务注入其他服务。AuthorService需要使用图书服务宣布AuthorService如下将允许

 AuthorService {
    定义bookService

依赖注入和域类标记库

 def bookService定义 买书bookService BuyBook这个)
    }
}
365bet地区由于365bet地区缺省情况下未启用此功能,如果要再次启用它,请查看春季自动装配域实例

如果在不同的程序包中定义了多个具有相同名称的服务,则与服务关联的默认bean名称可能会出现问题。例如,考虑应用程序定义了一个名为该应用程序使用名为ReportingUtilities该插件提供了一个名为com report util ReportingService.

其中每个的默认Bean名称为reportService365bet地区因此它们会彼此冲突。365bet地区通过为插件提供的服务更改默认的Bean名称来解决此问题,方法是在Bean名称之前加上插件名称

在上述情况下,reportServiceBean将是在应用程序和reportUtilitiesReportingServiceBean将是com report util ReportingService提供的课程ReportingUtilities插入

对于插件提供的所有服务Bean,如果应用程序中没有其他具有相同名称的服务或应用程序中的其他插件,则将创建一个不包含插件名称的Bean别名,并且该别名指向由所引用的Bean确实包含插件名称前缀的名称

例如,如果ReportingUtilities插件提供了一个名为com报告util AuthorService没有别的AuthorServicereportUtilitiesAuthorService这是一个实例com报告util AuthorService类,并且在上下文中定义了一个bean别名authorService指向那个豆