1. 程式人生 > 實用技巧 >SpringBoot - open-in-view 機制分析

SpringBoot - open-in-view 機制分析

open-in-view功能是指SpringBoot在請求開始前自動初始化一個數據庫會話,一般是EntityManager。

當這個功能啟用時,會輸出一條日誌:

2020-10-22 20:46:46.720 WARN 6484 --- [ restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning

-----open-in-view功能的啟動條件是:
1) 引用spring-boot-starter-jpa

2)或者引用spring-boot-starter-jdbc +hibernate

3)或者,直接引用hibernate +spring-orm

----這個功能的啟用過程:

1)在spring-boot-autoconfigure包中,META-INF/Spring.factories 檔案配置了啟動類:

org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
HibernateJpaAutoConfiguration:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration {

}

 從這個類的Condition條件可以看出其啟用條件:‘

當前classpath下要有LocalContainerEntityManagerFactoryBean類,這個類在org.springframework.orm.jpa包下,所以要引用spring-orm

EntityManager在javax.persistence下,SessionImplementor 是Hibernate的類

2)HibernateJpaAutoConfiguration引入了HibernateJpaConfiguration,HibernateJpaConfiguration的啟用條件是當前僅有一個DataSource bean

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {

HibernateJpaConfiguration的基類JpaBaseConfiguration定義了一個內部類

@Configuration(proxyBeanMethods = false)
	@ConditionalOnWebApplication(type = Type.SERVLET)
	@ConditionalOnClass(WebMvcConfigurer.class)
	@ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class })
	@ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class)
	@ConditionalOnProperty(prefix = "spring.jpa", name = "open-in-view", havingValue = "true", matchIfMissing = true)
	protected static class JpaWebConfiguration {

		private static final Log logger = LogFactory.getLog(JpaWebConfiguration.class);

		private final JpaProperties jpaProperties;

		protected JpaWebConfiguration(JpaProperties jpaProperties) {
			this.jpaProperties = jpaProperties;
		}

		@Bean
		public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() {
			if (this.jpaProperties.getOpenInView() == null) {
				logger.warn("spring.jpa.open-in-view is enabled by default. "
						+ "Therefore, database queries may be performed during view "
						+ "rendering. Explicitly configure spring.jpa.open-in-view to disable this warning");
			}
			return new OpenEntityManagerInViewInterceptor();
		}

		@Bean
		public WebMvcConfigurer openEntityManagerInViewInterceptorConfigurer(
				OpenEntityManagerInViewInterceptor interceptor) {
			return new WebMvcConfigurer() {

				@Override
				public void addInterceptors(InterceptorRegistry registry) {
					registry.addWebRequestInterceptor(interceptor);
				}

			};
		}

	}

這個類註冊了OpenEntityManagerInViewInterceptor 攔截器。OpenEntityManagerInViewInterceptor 用來在請求啟動前初始化EntityManager,在請求完成後關閉EntityManager

JpaBaseConfiguration 會自動建立一個事務管理器和EntityManagerFactory. OpenEntityManagerInViewInterceptor預設會採用這個EntityManagerFactory類建立EntityManager

@Bean
	@ConditionalOnMissingBean(TransactionManager.class)
	public PlatformTransactionManager transactionManager(
			ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
		JpaTransactionManager transactionManager = new JpaTransactionManager();
		transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
		return transactionManager;
	}

  

@Bean
	@Primary
	@ConditionalOnMissingBean({ LocalContainerEntityManagerFactoryBean.class, EntityManagerFactory.class })
	public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder factoryBuilder) {
		Map<String, Object> vendorProperties = getVendorProperties();
		customizeVendorProperties(vendorProperties);
		return factoryBuilder.dataSource(this.dataSource).packages(getPackagesToScan()).properties(vendorProperties)
				.mappingResources(getMappingResources()).jta(isJta()).build();
	}

LocalContainerEntityManagerFactoryBean 依賴DataSource,DataSourde的建立過程:

META-INF/Spring.factories 檔案配置了啟動類:org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ DataSource.class, EmbeddedDatabaseType.class })
@ConditionalOnMissingBean(type = "io.r2dbc.spi.ConnectionFactory")
@EnableConfigurationProperties(DataSourceProperties.class)
@Import({ DataSourcePoolMetadataProvidersConfiguration.class, DataSourceInitializationConfiguration.class })
public class DataSourceAutoConfiguration {
@Configuration(proxyBeanMethods = false)
@Conditional(EmbeddedDatabaseCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import(EmbeddedDataSourceConfiguration.class)
protected static class EmbeddedDatabaseConfiguration {

}

@Configuration(proxyBeanMethods = false)
@Conditional(PooledDataSourceCondition.class)
@ConditionalOnMissingBean({ DataSource.class, XADataSource.class })
@Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class,
DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.OracleUcp.class,
DataSourceConfiguration.Generic.class, DataSourceJmxConfiguration.class })
protected static class PooledDataSourceConfiguration {

}

 DataSourceAutoConfiguration 的啟用條件是,當前類路徑下存在DataSource(框架類)和org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType,EmbeddedDatabaseType是spring-jdbc下的類

所以,只要引用spring-jdbc報,DataSource自動初始話過程就會啟用。注意:spring-orm包引用了spring-jdbc。

DataSourceAutoConfiguration按照順序,如果當前存在簽入資料庫引用,則建立簽入資料來源, 否側嘗試建立
Hikari/Tomcat/Dbcp2/OracleUcp 等連線池型別的資料來源

-----------

只要專案引用了hibernate,spring-orm, 並且當前容器中只有一個DataSource bean, 這個open-in-view預設會啟用,如果需要禁用,則要顯示設定spring.jpa.open-in-view=false

-----------

可以在程式碼中設定spring.jpa.open-in-view預設值:

public static void main(String[] args) {

		new SpringApplicationBuilder(DemoApplication.class)
				.properties(props())
				.build()
				.run(args);

		//SpringApplication.run(DemoApplication.class, args);
	}

	private static Properties props() {
		Properties properties = new Properties();
		properties.setProperty("spring.jpa.open-in-view", "false");
		return properties;
	}