Spring Data JDBC參考文件 四

原標題:Spring認證|Spring Data JDBC參考文件四 (內容來源:Spring中國教育管理中心)

Spring Data JDBC參考文件 四

9。9。 生命週期事件

Spring Data JDBC 觸發釋出到ApplicationListener應用程式上下文中任何匹配bean 的事件。例如,在儲存聚合之前呼叫以下偵聽器:

@Bean

public ApplicationListener> loggingSaves() {

return event -> {

Object entity = event。getEntity();

LOG。info(“{} is getting saved。”, entity);

};

}

如果您只想處理特定域型別的事件,您可以從中派生您的偵聽器AbstractRelationalEventListener並覆蓋一個或多個onXXX方法,其中XXX代表事件型別。回撥方法只會被與域型別及其子型別相關的事件呼叫,因此您不需要進一步轉換。

public class PersonLoadListener extends AbstractRelationalEventListener {

@Override

protected void onAfterLoad(AfterLoadEvent personLoad) {

LOG。info(personLoad。getEntity());

}

}

下表描述了可用的事件:

Spring Data JDBC參考文件 四

生命週期事件依賴於ApplicationEventMulticaster,在這種情況下SimpleApplicationEventMulticaster可以使用 配置TaskExecutor,因此在處理事件時不提供任何保證。

9。9。1。 商店特定的 EntityCallbacks

Spring Data JDBC 使用EntityCallbackAPI 作為其審計支援並對以下回調做出反應:

Spring Data JDBC參考文件 四

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 extends EntityCallback {

/**

* 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 extends EntityCallback {

/**

* 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 onBeforeSave(T entity <2>, String collection <3>);

}

BeforeSaveCallback在儲存實體之前,在訂閱時呼叫的特定方法。發出一個可能被修改的例項。

在持久化之前的實體。

許多儲存特定引數,例如實體持久化到的集合。

可選的實體回撥引數由實現 Spring Data 模組定義並從EntityCallback。callback()。

實現適合您的應用程式需求的介面,如下例所示:

示例 62。 示例 BeforeSaveCallback

class DefaultingEntityCallback implements BeforeSaveCallback, 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 {

@Override

public Person onBeforeSave(Person person) {

return // 。。。

}

}

@Component

class DefaultingEntityCallback implements BeforeSaveCallback

Ordered {

@Override

public Object onBeforeSave(Person entity, String collection) {

// 。。。

}

@Override

public int getOrder() {

return 100;

}

}

@Configuration

public class EntityCallbackConfiguration {

@Bean

BeforeSaveCallback unorderedLambdaReceiverCallback() {

return (BeforeSaveCallback) it -> // 。。。

}

}

@Component

class UserCallbacks implements BeforeConvertCallback

BeforeSaveCallback {

@Override

public Person onBeforeConvert(User user) {

return // 。。。

}

@Override

public Person onBeforeSave(User user) {

return // 。。。

}

}

BeforeSaveCallback從@Order註釋中接收其命令。

BeforeSaveCallback透過Ordered介面實現接收其訂單。

BeforeSaveCallback使用 lambda 表示式。預設情況下無序並最後呼叫。請注意,由 lambda 表示式實現的回撥不會公開型別資訊,因此使用不可分配的實體呼叫這些會影響回撥吞吐量。使用classorenum為回撥 bean 啟用型別過濾。

在單個實現類中組合多個實體回撥介面。

Spring Data JDBC參考文件 四

Spring Data JDBC參考文件 四

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 {

@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 {

@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 {

public Email convert(String source) {

return Email。valueOf(source);

}

}

如果您編寫Converter的源型別和目標型別均為本機型別,我們無法確定是否應將其視為讀取轉換器或寫入轉換器。將轉換器例項註冊為兩者可能會導致不需要的結果。例如, aConverter是不明確的,儘管在編寫時嘗試將所有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 { … }

// Read converter as only the source type is one that can be handled natively

class MyConverter implements Converter { … }

9。12。 日誌記錄

Spring Data JDBC 本身幾乎沒有日誌記錄。相反,JdbcTemplate發出 SQL 語句的機制提供了日誌記錄。因此,如果您想檢查運行了哪些 SQL 語句,請啟用 SpringNamedParameterJdbcTemplate或MyBatis 的日誌記錄。

9。13。 交易性

儲存庫例項上的 CRUD 方法預設是事務性的。對於讀取操作,事務配置readOnly標誌設定為true。所有其他人都使用普通@Transactional註釋進行配置,以便應用預設事務配置。有關詳細資訊,請參閱SimpleJdbcRepository。 如果您需要為儲存庫中宣告的方法之一調整事務配置,請在儲存庫介面中重新宣告該方法,如下所示:

示例 64。 CRUD 的自定義事務配置

public interface UserRepository extends CrudRepository {

@Override

@Transactional(timeout = 10)

public List 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);

}

}

Spring Data JDBC參考文件 四

前面的示例導致呼叫addRoleToAllUsers(…)在事務內執行(參與現有事務或在沒有執行的情況下建立新事務)。儲存庫的事務配置被忽略,因為外部事務配置決定了要使用的實際儲存庫。請注意,您必須顯式啟用或使用@EnableTransactionManagement以獲取 Facades 工作的基於註釋的配置。請注意,前面的示例假定您使用元件掃描。

9。13。1。 事務查詢方法

要讓您的查詢方法具有事務性,請@Transactional在您定義的儲存庫介面處使用,如以下示例所示:

示例 66。在查詢方法中使用 @Transactional

@Transactional(readOnly = true)

public interface UserRepository extends CrudRepository {

List 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,審計基礎結構需要以某種方式瞭解當前主體。為此,我們提供了一個AuditorAwareSPI 介面,您必須實現該介面以告知基礎設施當前與應用程式互動的使用者或系統是誰。泛型型別T定義了用什麼型別註釋的屬性@CreatedBy或@LastModifiedBy必須是什麼型別。

以下示例顯示了使用 Spring SecurityAuthentication物件的介面的實現:

例 69。AuditorAware基於 Spring Security 的實現

class SpringSecurityAuditorAware implements AuditorAware {

@Override

public Optional 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資訊。我們提供了一個ReactiveAuditorAwareSPI 介面,您必須實現該接口才能告訴基礎設施當前與應用程式互動的使用者或系統是誰。泛型型別T定義了用什麼型別註釋的屬性@CreatedBy或@LastModifiedBy必須是什麼型別。

以下示例顯示了使用響應式 Spring SecurityAuthentication物件的介面的實現:

例 70。ReactiveAuditorAware基於 Spring Security 的實現

class SpringSecurityAuditorAware implements ReactiveAuditorAware {

@Override

public Mono 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 auditorProvider() {

return new AuditorAwareImpl();

}

}

如果您向 公開型別AuditorAware為 的bean ApplicationContext,審計基礎結構會自動選取它並使用它來確定要在域型別上設定的當前使用者。如果您在 中註冊了多個實現,則ApplicationContext可以透過顯式設定 的auditorAwareRef屬性來選擇要使用的一個@EnableJdbcAuditing。

內容提示:本文(Spring Data JDBC參考文件)未完待續......