原标题:Spring认证|Spring Data JDBC参考文档四 (内容来源:Spring中国教育管理中心)
9.9. 生命周期事件
Spring Data JDBC 触发发布到ApplicationListener应用程序上下文中任何匹配bean 的事件。例如,在保存聚合之前调用以下侦听器:
@Bean
public ApplicationListener<BeforeSaveEvent<Object>> loggingSaves() {
return event -> {
Object entity = event.getEntity();
LOG.info("{} is getting saved.", entity);
};
}
如果您只想处理特定域类型的事件,您可以从中派生您的侦听器
AbstractRelationalEventListener并覆盖一个或多个onXXX方法,其中XXX代表事件类型。回调方法只会被与域类型及其子类型相关的事件调用,因此您不需要进一步转换。
public class PersonLoadListener extends AbstractRelationalEventListener<Person> {
@Override
protected void onAfterLoad(AfterLoadEvent<Person> personLoad) {
LOG.info(personLoad.getEntity());
}
}
下表描述了可用的事件:
生命周期事件依赖于
ApplicationEventMulticaster,在这种情况下SimpleApplicationEventMulticaster可以使用 配置TaskExecutor,因此在处理事件时不提供任何保证。
9.9.1. 商店特定的 EntityCallbacks
Spring Data JDBC 使用EntityCallbackAPI 作为其审计支持并对以下回调做出反应:
9.10. 实体回调
Spring Data 基础设施提供了在调用某些方法之前和之后修改实体的钩子。那些所谓的EntityCallback实例提供了一种方便的方法来检查和潜在地以回调风格修改实体。
AnEntityCallback看起来很像一个专门的ApplicationListener. 一些 Spring Data 模块发布BeforeSaveEvent允许修改给定实体的存储特定事件(例如)。在某些情况下,例如使用不可变类型时,这些事件可能会导致麻烦。此外,事件发布依赖于
ApplicationEventMulticaster. 如果使用异步配置TaskExecutor它可能会导致不可预测的结果,因为事件处理可以分叉到线程上。
实体回调提供了同步和反应式 API 的集成点,以保证在处理链中定义明确的检查点按顺序执行,返回可能修改的实体或反应式包装器类型。
实体回调通常按 API 类型分隔。这种分离意味着同步 API 仅考虑同步实体回调,而反应式实现仅考虑反应式实体回调。
Spring Data Commons 2.2 引入了实体回调 API。这是应用实体修改的推荐方式。在调用可能已注册的实例之前,ApplicationEvents仍会发布特定于现有商店的信息。EntityCallback
9.10.1. 实现实体回调
AnEntityCallback通过其泛型类型参数直接与其域类型相关联。每个 Spring Data 模块通常带有一组EntityCallback涵盖实体生命周期的预定义接口。
例 60. 解剖 EntityCallback
@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before a domain object is saved.
* Can return either the same or a modified instance.
*
* @return the domain object to be persisted.
*/
T onBeforeSave(T entity <2>, String collection <3>);
}
BeforeSaveCallback在保存实体之前要调用的特定方法。返回一个可能被修改的实例。
在持久化之前的实体。
许多存储特定参数,例如实体持久化到的集合。
例 61. 反应式的剖析 EntityCallback
@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked on subscription, before a domain object is saved.
* The returned Publisher can emit either the same or a modified instance.
*
* @return Publisher emitting the domain object to be persisted.
*/
Publisher<T> onBeforeSave(T entity <2>, String collection <3>);
}
BeforeSaveCallback在保存实体之前,在订阅时调用的特定方法。发出一个可能被修改的实例。
在持久化之前的实体。
许多存储特定参数,例如实体持久化到的集合。
可选的实体回调参数由实现 Spring Data 模块定义并从EntityCallback.callback().
实现适合您的应用程序需求的接口,如下例所示:
示例 62. 示例 BeforeSaveCallback
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered {
@Override
public Object onBeforeSave(Person entity, String collection) {
if(collection == "user") {
return // ...
}
return // ...
}
@Override
public int getOrder() {
return 100;
}
}
根据您的要求实现回调。
如果存在多个相同域类型的实体回调,则可能对实体回调进行排序。排序遵循最低优先级。
9.10.2. 注册实体回调
EntityCallback如果 bean 在ApplicationContext. 大多数模板 API 已经实现ApplicationContextAware,因此可以访问ApplicationContext
以下示例解释了一组有效的实体回调注册:
示例 63. EntityCallbackBean 注册示例
@Order(1)
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
Ordered {
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100;
}
}
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() {
return (BeforeSaveCallback<Person>) it -> // ...
}
}
@Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> {
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
BeforeSaveCallback从@Order注释中接收其命令。
BeforeSaveCallback通过Ordered接口实现接收其订单。
BeforeSaveCallback使用 lambda 表达式。默认情况下无序并最后调用。请注意,由 lambda 表达式实现的回调不会公开类型信息,因此使用不可分配的实体调用这些会影响回调吞吐量。使用classorenum为回调 bean 启用类型过滤。
在单个实现类中组合多个实体回调接口。
9.11. 自定义转化
Spring Data JDBC 允许注册自定义转换器以影响值在数据库中的映射方式。目前,转换器仅应用于属性级别。
9.11.1. 使用注册的 Spring 转换器编写属性
以下示例显示了Converter从Boolean对象转换为String值的实现:
import org.springframework.core.convert.converter.Converter;
@WritingConverter
public class BooleanToStringConverter implements Converter<Boolean, String> {
@Override
public String convert(Boolean source) {
return source != null && source ? "T" : "F";
}
}
这里有几件事需要注意:Boolean和String都是简单类型,因此 Spring Data 需要提示此转换器应应用的方向(读取或写入)。通过对这个转换器进行注释,@WritingConverter您可以指示 Spring DataBoolean像String在数据库中一样编写每个属性。
9.11.2. 使用 Spring 转换器读取
以下示例显示了Converter从 aString转换为Boolean值的a实现:
@ReadingConverter
public class StringToBooleanConverter implements Converter<String, Boolean> {
@Override
public Boolean convert(String source) {
return source != null && source.equalsIgnoreCase("T") ? Boolean.TRUE : Boolean.FALSE;
}
}
这里有几件事需要注意:String和Boolean都是简单类型,因此 Spring Data 需要提示此转换器应应用的方向(读取或写入)。通过对这个转换器进行注释,@ReadingConverter您可以指示 Spring Data 转换String数据库中应该分配给Boolean属性的每个值。
9.11.3. 注册 Spring 转换器JdbcConverter
class MyJdbcConfiguration extends AbstractJdbcConfiguration {
// …
@Overwrite
@Bean
public JdbcCustomConversions jdbcCustomConversions() {
return new JdbcCustomConversions(Arrays.asList(new BooleanToStringConverter(), new StringToBooleanConverter()));
}
}
以下 SpringConverter实现示例从 aString转换为自定义Email值对象:
@ReadingConverter
public class EmailReadConverter implements Converter<String, Email> {
public Email convert(String source) {
return Email.valueOf(source);
}
}
如果您编写Converter的源类型和目标类型均为本机类型,我们无法确定是否应将其视为读取转换器或写入转换器。将转换器实例注册为两者可能会导致不需要的结果。例如, aConverter<String, Long>是不明确的,尽管在编写时尝试将所有String实例转换为Long实例可能没有意义。为了让你强制基础设施注册一个转换器,只有一个办法,我们提供@ReadingConverter并@WritingConverter在转换器实现使用注解。
转换器需要进行显式注册,因为不会从类路径或容器扫描中提取实例,以避免不必要的转换服务注册以及此类注册产生的副作用。转换器注册CustomConversions为中央工具,允许根据源和目标类型注册和查询已注册的转换器。
CustomConversions 附带一组预定义的转换器注册:
JSR-310 转换器,用于在java.time,java.util.Date和String类型之间进行转换。
不推荐使用:Joda 时间转换器,用于在org.joda.time、JSR-310 和java.util.Date.
不推荐使用:ThreeTenBackport 转换器,用于在org.joda.time、JSR-310 和java.util.Date.
本地时间类型(例如LocalDateTimeto java.util.Date)的默认转换器依赖于系统默认时区设置在这些类型之间进行转换。您可以通过注册您自己的转换器来覆盖默认转换器。
转换器消歧
通常,我们会检查Converter它们相互转换的源和目标类型的实现。根据其中一个是否是底层数据访问 API 可以本地处理的类型,我们将转换器实例注册为读取或写入转换器。以下示例显示了写入和读取转换器(注意区别在于 上的限定符的顺序Converter):
// Write converter as only the target type is one that can be handled natively
class MyConverter implements Converter<Person, String> { … }
// Read converter as only the source type is one that can be handled natively
class MyConverter implements Converter<String, Person> { … }
9.12. 日志记录
Spring Data JDBC 本身几乎没有日志记录。相反,JdbcTemplate发出 SQL 语句的机制提供了日志记录。因此,如果您想检查运行了哪些 SQL 语句,请激活
SpringNamedParameterJdbcTemplate或MyBatis 的日志记录。
9.13. 交易性
存储库实例上的 CRUD 方法默认是事务性的。对于读取操作,事务配置readOnly标志设置为true。所有其他人都使用普通@Transactional注释进行配置,以便应用默认事务配置。有关详细信息,请参阅SimpleJdbcRepository. 如果您需要为存储库中声明的方法之一调整事务配置,请在存储库接口中重新声明该方法,如下所示:
示例 64. CRUD 的自定义事务配置
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// Further query method declarations
}
前面的内容会导致findAll()方法以 10 秒的超时时间运行并且没有readOnly标志。
另一种改变事务行为的方法是使用通常覆盖多个存储库的外观或服务实现。其目的是为非 CRUD 操作定义事务边界。以下示例显示了如何创建这样的外观:
示例 65. 使用 Facade 为多个存储库调用定义事务
@Service
class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Autowired
public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
前面的示例导致调用addRoleToAllUsers(…)在事务内运行(参与现有事务或在没有运行的情况下创建新事务)。存储库的事务配置被忽略,因为外部事务配置决定了要使用的实际存储库。请注意,您必须显式激活<tx:annotation-driven />或使用@
EnableTransactionManagement以获取 Facades 工作的基于注释的配置。请注意,前面的示例假定您使用组件扫描。
9.13.1. 事务查询方法
要让您的查询方法具有事务性,请@Transactional在您定义的存储库接口处使用,如以下示例所示:
示例 66.在查询方法中使用 @Transactional
@Transactional(readOnly = true)
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByLastname(String lastname);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
通常,您希望将该readOnly标志设置为 true,因为大多数查询方法只读取数据。与此相反,deleteInactiveUsers()使用@Modifying注释并覆盖事务配置。因此,该方法的readOnly标志设置为false。
将事务用于只读查询绝对是合理的,我们可以通过设置readOnly标志来标记它们。但是,这并不作为检查您没有触发操作查询(尽管某些数据库拒绝INSERT和UPDATE只读事务中的语句)。相反,该readOnly标志作为提示传播给底层 JDBC 驱动程序以进行性能优化。
9.14. 审计
9.14.1. 基本
Spring Data 提供了复杂的支持,以透明地跟踪谁创建或更改了实体以及更改发生的时间。要从该功能中受益,您必须为实体类配备审计元数据,这些元数据可以使用注释或通过实现接口来定义。此外,必须通过 Annotation 配置或 XML 配置启用审计以注册所需的基础架构组件。有关配置示例,请参阅特定于商店的部分。
仅跟踪创建和修改日期的应用程序不需要指定AuditorAware.
基于注释的审计元数据
我们提供@CreatedBy并@LastModifiedBy捕获创建或修改实体的用户,@CreatedDate并@LastModifiedDate捕获更改发生的时间。
示例 67. 一个被审计的实体
class Customer {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
// … further properties omitted
}
如您所见,可以有选择地应用注释,具体取决于您要捕获的信息。进行更改时捕获的注释可用于 Joda-Time DateTime、旧版 JavaDate和Calendar、JDK8 日期和时间类型以及long或Long.
审计元数据不一定需要存在于根级实体中,但可以添加到嵌入式实体中(取决于实际使用的存储),如下面的截图所示。
示例 68. 审计嵌入实体中的元数据
class Customer {
private AuditMetadata auditingMetadata;
// … further properties omitted
}
class AuditMetadata {
@CreatedBy
private User user;
@CreatedDate
private Instant createdDate;
}
基于接口的审计元数据
如果您不想使用注释来定义审计元数据,您可以让您的域类实现该Auditable接口。它公开了所有审计属性的 setter 方法。
AuditorAware
如果您使用@CreatedBy或@LastModifiedBy,审计基础结构需要以某种方式了解当前主体。为此,我们提供了一个AuditorAware<T>SPI 接口,您必须实现该接口以告知基础设施当前与应用程序交互的用户或系统是谁。泛型类型T定义了用什么类型注释的属性@CreatedBy或@LastModifiedBy必须是什么类型。
以下示例显示了使用 Spring SecurityAuthentication对象的接口的实现:
例 69.AuditorAware基于 Spring Security 的实现
class SpringSecurityAuditorAware implements AuditorAware<User> {
@Override
public Optional<User> getCurrentAuditor() {
return Optional.ofNullable(SecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
该实现访问AuthenticationSpring Security 提供的对象并查找UserDetails您在UserDetailsService实现中创建的自定义实例。我们在这里假设您通过UserDetails实现公开域用户,但根据Authentication发现,您也可以从任何地方查找它。
ReactiveAuditorAware
使用反应式基础架构时,您可能希望使用上下文信息来提供@CreatedBy或提供@LastModifiedBy信息。我们提供了一个ReactiveAuditorAware<T>SPI 接口,您必须实现该接口才能告诉基础设施当前与应用程序交互的用户或系统是谁。泛型类型T定义了用什么类型注释的属性@CreatedBy或@LastModifiedBy必须是什么类型。
以下示例显示了使用响应式 Spring SecurityAuthentication对象的接口的实现:
例 70.ReactiveAuditorAware基于 Spring Security 的实现
class SpringSecurityAuditorAware implements ReactiveAuditorAware<User> {
@Override
public Mono<User> getCurrentAuditor() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.filter(Authentication::isAuthenticated)
.map(Authentication::getPrincipal)
.map(User.class::cast);
}
}
该实现访问AuthenticationSpring Security 提供的对象并查找UserDetails您在UserDetailsService实现中创建的自定义实例。我们在这里假设您通过UserDetails实现公开域用户,但根据Authentication发现,您也可以从任何地方查找它。
9.15. JDBC审计
为了激活审计,添加@EnableJdbcAuditing到您的配置中,如以下示例所示:
示例 71. 使用 Java 配置激活审计
@Configuration
@EnableJdbcAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> auditorProvider() {
return new AuditorAwareImpl();
}
}
如果您向 公开类型AuditorAware为 的bean ApplicationContext,审计基础结构会自动选取它并使用它来确定要在域类型上设置的当前用户。如果您在 中注册了多个实现,则ApplicationContext可以通过显式设置 的auditorAwareRef属性来选择要使用的一个@EnableJdbcAuditing。
内容提示:本文(Spring Data JDBC参考文档)未完待续......