1. OSIV(Open Session In View) ๋??

OSIV(Open Session In View)์ ์์์ฑ ์ปจํ ์คํธ ๊ด๋ฆฌ ์์ญ์ ๋ทฐ(View)๊น์ง ํ์ฅํ๋ ๊ธฐ๋ฅ์ด๋ค. Open Session In View๋ผ ๋ถ๋ฆฌ๋ ์ด์ ๋ JPA์ ๊ตฌํ์ฒด์ธ Hibernate ์์ EntityManager์ ๊ตฌํ์ฒด๋ก Session ์ ์ฌ์ฉํ๋ค. JPA์์๋ OEIV(Open-EntityManager-In-View) ๋ก ๋ถ๋ฅด์ง๋ง ํต์์ ์ผ๋ก Open-Session-In-View ๋ก ๋ถ๋ฅธ๋ค.
2. Spring-Boot ์ ํ๋กํผํฐ ๊ธฐ๋ฐ OSIV ์ค์ ๊ณผ์
Spring-Boot ๋ ํ๋กํผํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์๋ ์ค์ ์ ์ง์ํ๋ค. JpaBaseconfiguration ์ถ์ ํด๋์ค์ nested static class ์ธ JpaWebConfiguration ์์ OSIV ๋ฅผ ๋ด๋นํ๋ ์ธํฐ์ ํฐ์ธ OpenEntityanagerInViewInterceptor ๋ฅผ ํ์ฑ ์ฌ๋ถ๋ฅผ ๊ด๋ฆฌํ๋ค. spring.jpa.open-in-view ํ๋กํผํฐ๋ฅผ ํตํด ๊ด๋ฆฌ๋๋ฉฐ ๋ณ๋์ ์ค์ ์ด ์์ ๊ฒฝ์ฐ์๋ OpenEntityanagerInViewInterceptor ๋ ๋น์ผ๋ก ๋ฑ๋ก๋๊ณ ์ธํฐ์ ํฐ๋ก ๊ด๋ฆฌ๋๋ค.
spring.jpa.open-in-view: false # default value = true
package org.springframework.boot.autoconfigure.orm.jpa;
// imports ...
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(JpaProperties.class)
public abstract class JpaBaseConfiguration implements BeanFactoryAware {
private final DataSource dataSource;
// ์ฌ๊ธฐ์ JpaProperties
private final JpaProperties properties;
private final JtaTransactionManager jtaTransactionManager;
private ConfigurableListableBeanFactory beanFactory;
@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;
}
// ์ค์ OSIV ๋ฅผ ๋ด๋นํ๋ OpenEntityManagerInViewInterceptor ๊ฐ ๋น์ผ๋ก ์ ์ธ๋๋ ๋ถ๋ถ
@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);
}
};
}
}
}
3. OpenEntityManagerInViewInterceptor ์ ๊ธฐ๋ณธ ๋์์ ํ์ธํด๋ณด์.
์ค์ ์ฝ๋๋ฅผ ํ์ธํด๋ณด๋ฉด ์์ฒญ์ด ๋ค์ด์ฌ ๊ฒฝ์ฐ์๋ ์ํฐํฐ ๋งค๋์ ๋ฅผ ์์ฑํ๊ณ , ์์ฒญ์ด ์๋ฃ๋๋ ๊ฒฝ์ฐ์๋ ์ํฐํฐ ๋งค๋์ ๋ฅผ ์ข ๋ฃํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค. ์ด์ธ์๋ ์ํฐํฐ ๋งค๋์ ๊ฐ ๊ด๋ฆฌ๋๊ธฐ ์ํ TransactionSynchronizationManager, AsyncRequestInterceptor ์ ๊ฐ์ด ํธ๋์ญ์ ๋๊ธฐํ, ๋น๋๊ธฐ๋ฅผ ์ํ ์์ ์ ์ํ ์ค์ ์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
package org.springframework.orm.jpa.support;
// imports ...
public class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor {
public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE";
@Override
public void preHandle(WebRequest request) throws DataAccessException {
String key = getParticipateAttributeName();
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) {
return;
}
EntityManagerFactory emf = obtainEntityManagerFactory();
if (TransactionSynchronizationManager.hasResource(emf)) {
// ํธ๋์ญ์
๋๊ธฐํ์ ๊ด๋ จ๋ ๋ก์ง
} else {
logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewInterceptor");
// EntityManager ๊ฐ ์์ฑ๋๋ค.
try {
EntityManager em = createEntityManager();
EntityManagerHolder emHolder = new EntityManagerHolder(em);
TransactionSynchronizationManager.bindResource(emf, emHolder);
AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder);
asyncManager.registerCallableInterceptor(key, interceptor);
asyncManager.registerDeferredResultInterceptor(key, interceptor);
} catch (PersistenceException ex) {
throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
}
}
}
@Override
public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException {
if (!decrementParticipateCount(request)) {
EntityManagerHolder emHolder = (EntityManagerHolder)
TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory());
logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewInterceptor");
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); // 2. ์ํฐํฐ ๋งค๋์ ๋ฅผ ๋ซ๋๋ค.
}
}
}
4. ๋ง์ฝ Filter ์์ ์ํฐํฐ๋ฅผ ๊ด๋ฆฌ๋์ด์ผ ํ๋ค๋ฉด?
Spring Security ์ ๊ฐ์ด FilterChain ๊ธฐ๋ฐ์์ ์ํฐํฐ๋ฅผ ์ด๊ธฐํํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ์๋ค. ์ผ๋ฐ์ ์ผ๋ก Open-Session-In-View ๋ ์ํฐ ํจํด์ด๋ผ ๋งํ๋ค. ์์ฒญ์ด ์์, ์ข ๋ฃ๋๋ ์์ ๊น์ง DB Connection ์ ์ก๊ณ ์์ด ์ค์๊ฐ ์๋น์ค์ ๊ฐ์ด ํธ๋ํฝ์ ๋ฏผ๊ฐํ ์๋น์ค์ ๊ฒฝ์ฐ์ ์ฑ๋ฅ ์ ํ ์์๊ฐ ๋ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค. ํ์ง๋ง ์ฌ๋ด์ ์์คํ ์ด ๋ ๊ฑฐ์ ์ฝ๋๋ก ์ธํด ํํฐ ์ชฝ์์ ์ํฐํฐ๋ฅผ ์ด๊ธฐํํด์ผ ๋์ ๋ถ๊ฐํผํ๊ฒ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด OpenEntityManagerInViewFilter ์ ์ ์ธํด์ ์ฌ์ฉํด์ผ ํ๋ค. OpenEntityManagerInViewFilter ๋ ์คํ๋ง์์ ์ง์ํด์ฃผ๊ธฐ ๋๋ฌธ์ ํด๋น ๋น์ ๋ฑ๋กํด์ ์ฌ์ฉํ๋๋ก ํ์.
package org.springframework.orm.jpa.support;
// ์ฌ๋ฌ๊ฐ์ง import
public class OpenEntityManagerInViewFilter extends OncePerRequestFilter {
// ๋น์ฆ๋์ค ๋ก์ง
}
// source : https://tecoble.techcourse.co.kr/post/2020-09-20-entity-lifecycle-2/
@Component
@Configuration
public class OpenEntityManagerConfig {
@Bean
public FilterRegistrationBean<OpenEntityManagerInViewFilter> openEntityManagerInViewFilter() {
FilterRegistrationBean<OpenEntityManagerInViewFilter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new OpenEntityManagerInViewFilter());
filterFilterRegistrationBean.setOrder(Integer.MIN_VALUE); // ์ต์ฐ์ ์์๋ก Filter ๋ฑ๋ก
return filterFilterRegistrationBean;
}
}
5. ๋ด๊ฐ ์๊ฐํ๋ open-session-in-view
ํํ open-session-in-view ๋ฅผ ์ํฐ ํจํด์ด๋ผ ๋ถ๋ฅธ๋ค. ์ด์ ์ ์ธ๊ธํ๋ฏ์ด ์ค์๊ฐ ํธ๋ํฝ์ด ์ค์ํ ์๋น์ค์ ๊ฒฝ์ฐ, ์์ฒญ ์์ ์ EntityManager ๊ฐ ์์ฑ๋๋ฉด์ DB Connection Pool ์ Connection ์ ๋ถ์ก๊ณ ์์ด Connection Pool ์ด ๊ณ ๊ฐ๋ ์ ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค.
open-session-in-view ๋ฅผ ํ์ฑํ ํ ๊ฒฝ์ฐ, DB ์์ ์ด ๋ชจ๋ ์๋ฃ๋๊ณ ๋ค๋ฅธ ์ธ๋ถ API ์์ฒญ์ ๋๊ธฐํ๋ ์๊ฐ๊น์ง Connection ์ ๋ฐํํ์ง ์๋๋ค. ์ด์ ๊ฐ์ ๋ณ๋ชฉ์ง์ ์ด ์๊ธฐ๊ณ ํด๋น ๋ก์ง์ด ๋ง์ ์ฌ์ฉ์๊ฐ ์ฌ์ฉํ๋ค๋ฉด ConnectionPool ์ Connection ์ ๋ชจ๋ ์ก๋๋ค๋ฉด Connection ์ ๋ฐํํ๊ธฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์ด๋ ์ฌ์ฉ์์๊ฒ ๋์ ์ฌ์ฉ ๊ฒฝํ์ ์ ๊ณตํด์ฃผ๊ธฐ ๋๋ฌธ์ ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด ์ฌ์ฉํ์ง ์๋ ๊ฒ์ด ์ ํฉํ ๊ฒ ๊ฐ๋ค.
hikariCP ์์ ์ ๊ณตํ๋ ํ๋กํผํฐ๋ค์ ๊ธฐ๋ณธ ๊ฐ ์ ๋๋ ์ฐธ๊ณ ๋ก ์๊ณ ์์.
# ์ฃผ์ : DB ๋จ์ - s, Spring Boot ๋จ์ - ms
# Spring Boot ์ค์ ์ Database ์ค์ ๋ณด๋ค ์ ๊ฒ ์ค์ ํ๋ ๊ฒ์ ๊ถ์ฅ
spring.datasource.hikari.minimumIdle=30000 #30์ด
spring.datasource.hikari.connectionTimeout=30000 #30์ด
spring.datasource.hikari.maximumPoolSize=10
spring.datasource.hikari.idleTimeout=600000 # 10๋ถ
spring.datasource.hikari.maxLifeTime=1800000 # 30๋ถ
spring.datasource.hikari.autoCommit=true
๋น๋ก thymeleaf, jsp ์ ๊ฐ์ ํ ํ๋ฆฟ ์์ง์ ์ด๋๋ฏผ ํ์ด์ง๋ก ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์ ํธ์์ฑ์ ๊ฐ์ ธ๋คw์ฃผ๋ ์ฅ์ ์ด ์์ง๋ง ํธ๋ฆฌํจ์ผ๋ก ์ธํด ํ ํ๋ฆฟ ์์ง์ ์์กด์ ์ธ ์ฝ๋๊ฐ ๋ฐ์ํ๊ณ vue.js ๋ react ๋ก ์ ํํ ๋์ ๊ณต์๊ฐ ๋์ด๋ ์ ์๋ค. ๋ง์ฝ ๊ธฐ์กด thymeleaf, jsp ์ ๊ฐ์ ํ ํ๋ฆฟ ์์ง๊ณผ open-session-in-view ํ์ฑํํด์ ๊ฐ๋ฐํ๊ณ ์๋ค๋ฉด ์ต๋ํ ์ํฐํฐ ์ด๊ธฐํ๋ฅผ ํ ํ๋ฆฟ ์์ง์์ ์ฌ์ฉํ์ง ์๋๋ก ํ๊ณ , ์ํฐํฐ ๋์ ์ DTO ๋ฅผ ํ์ฉํ๋ ๋ฐฉํฅ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ์์ผ๋ก ์ฝ๋ ์์ ์ ํ๋ ๊ฒ์ด ๋ฎ์ ์์กด์ฑ์ ๊ธฐ๋ฐํด์ ์ ์ง๋ณด์ํ๊ธฐ ์ฉ์ดํ์ง ์์๊น ์ถ๋ค.
- ์ ๊ท ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ค๋ฉด open-session-in-view ๋นํ์ฑํ ํ๊ธฐ
- ๊ธฐ์กด ํ๋ก์ ํธ์์ ์ฌ์ฉํ๋ค๋ฉด open-session-in-view ๋ฅผ ๋นํ์ฑํํ๋ ๋ฐฉํฅ์ผ๋ก ์์ ํ๊ธฐ (e.g. DTO)
6. summary
- OSIV ๋ ์์์ฑ ์ปจํ ์คํธ์ ๋ผ์ดํ ์ฌ์ดํด์ ์์ฒญ, ์๋ต์ด ๋๋ ๋๊น์ง ํ์ฑํํ๋ ์ ๋ต์ ๋งํ๋ค.
- OSIV ๋ OpenEntityManagerInViewInterceptor, OpenEntityManagerInViewFilter ๋ฅผ ํตํด ์์ํ ๋ฒ์๊ฐ ํ์ฅ๋๋ค.
- OSIV ๋ ๋ค๋ฅธ ๋ก์ง์ ์ฒ๋ฆฌ๊ฐ ๊ธธ์ด์ง๋๋งํผ Connection ์ ๋ถ์ก๊ณ ์์ด ์ปค๋ฅ์ ๊ณ ๊ฐ์ด ๋ ์ ์๋ ๋ฌธ์ ๋ก ์ธํด ์ํฐ ํจํด์ด๋ค. ํน๋ณํ ์ด์ ๊ฐ ์๋ค๋ฉด ๋นํ์ฑํํด์ ์ฌ์ฉํ์. (๋นํ์ฑํ ํ ๊ฒฝ์ฐ, ์์์ฑ ์ปจํ ์คํธ๋ @Transactional ๋ด๋ถ์์๋ง ๊ด๋ฆฌ๋๋ค.)
Reference
'๐ backend > spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| hibernate.query.in_clause_parameter_padding = true ? (1) | 2024.03.06 |
|---|---|
| JPA ์ UUID version 7 ์ ์ฉํ๊ธฐ (0) | 2024.03.04 |
| [Spring] Spring AOP ๋์ ์์๋ณด๊ธฐ (0) | 2024.03.04 |
| QueryDSL transform() ์์ ๊ผญ @Transactional ์ ์ฌ์ฉํด์ผ ํ๋ ์ด์ (0) | 2024.01.29 |
| ํ ์คํธ์์ @Sql ๋ก ํ ์คํธ ๋ฐ์ดํฐ๊ฐ ๋ค์ด๊ฐ์ง ์์๋ ์ด์ (with. custom TestExecutionListener) (1) | 2024.01.24 |
