/*
* Copyright 2011-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kr.debop4j.data.hibernate.unitofwork;
import kr.debop4j.core.AutoCloseableAction;
import kr.debop4j.core.Local;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.concurrent.ThreadSafe;
import static kr.debop4j.core.Guard.shouldNotBeNull;
/**
* Unit of Work 패턴을 구현한 Static 클래스입니다.
*
* @author 배성혁 ( sunghyouk.bae@gmail.com )
* @since 12. 12. 18
*/
@Component
@Slf4j
@ThreadSafe
public final class UnitOfWorks {
private UnitOfWorks() { }
static {
log.info("UnitOfWorks 인스턴스가 생성되었습니다.");
}
private static final String UNIT_OF_WORK_NOT_STARTED = "UnitOfWorks가 시작되지 않았습니다. 사용 전에 UnitOfWorks.start()를 호출하세요.";
private static volatile IUnitOfWork globalNonThreadSafeUnitOfWork;
private static volatile IUnitOfWorkFactory unitOfWorkFactory;
/** UnitOfWork 가 이미 시작되었는지 확인한다. */
public static synchronized boolean isStarted() {
return globalNonThreadSafeUnitOfWork != null || Local.get(IUnitOfWork.CURRENT_UNIT_OF_WORK_KEY) != null;
}
/** 현재 시작된 {@link IUnitOfWork}의 인스턴스 ({@link UnitOfWorkAdapter}를 반환합니다. */
public static synchronized IUnitOfWork getCurrent() {
if (!isStarted())
throw new HibernateException(UNIT_OF_WORK_NOT_STARTED);
if (globalNonThreadSafeUnitOfWork != null)
return globalNonThreadSafeUnitOfWork;
return Local.get(IUnitOfWork.CURRENT_UNIT_OF_WORK_KEY, IUnitOfWork.class);
}
/**
* Gets current session factory.
*
* @return the current session factory
*/
public static synchronized SessionFactory getCurrentSessionFactory() {
return getUnitOfWorkFactory().getSessionFactory();
}
/**
* Gets current session.
*
* @return the current session
*/
public static synchronized Session getCurrentSession() {
return getUnitOfWorkFactory().getCurrentSession();
}
/**
* Gets unit of work factory.
*
* @return {@link IUnitOfWorkFactory} instance.
*/
public static synchronized IUnitOfWorkFactory getUnitOfWorkFactory() {
if (unitOfWorkFactory == null)
throw new RuntimeException("Spring 환경설정에서 UnitOfWorks를 ComponentScan에 추가해주세요.");
return unitOfWorkFactory;
}
/**
* Spring으로부터 {@link IUnitOfWorkFactory}를 injection 을 받습니다.<p/>
* 참고 : http://debop.blogspot.kr/2013/05/spring-framework-static-field-injection.html
*
* @param factory {@link IUnitOfWorkFactory} instance.
*/
@Autowired
public void injectUnitOfWorkFactory(IUnitOfWorkFactory factory) {
log.info("Spring에서 UnitOfWorkFactory를 인젝션합니다. unitOfWorkFactory=[{}]", factory);
unitOfWorkFactory = factory;
}
/**
* Sets unit ozf work factory.
*
* @param factory the factory
*/
public static synchronized void setUnitOfWorkFactory(IUnitOfWorkFactory factory) {
log.info("UnitOfWorkFactory를 설정합니다. unitOfWorkFactory=[{}]", factory);
unitOfWorkFactory = factory;
}
/**
* Set current unit of work.
*
* @param unitOfWork the unit of work
*/
public static void setCurrent(IUnitOfWork unitOfWork) {
if (log.isDebugEnabled())
log.debug("현 Thread Context의 UnitOfWork 인스턴스를 설정합니다. unitOfWork=[{}]", unitOfWork);
Local.put(IUnitOfWork.CURRENT_UNIT_OF_WORK_KEY, unitOfWork);
}
/**
* Register global unit of work.
*
* @param globalUnitOfWork the global unit of work
* @return the auto closeable action
*/
public static synchronized AutoCloseableAction registerGlobalUnitOfWork(IUnitOfWork globalUnitOfWork) {
if (log.isDebugEnabled())
log.debug("전역 IUnitOfWork를 설정합니다. globalUnitOfWork=[{}]", globalUnitOfWork);
globalNonThreadSafeUnitOfWork = globalUnitOfWork;
return new AutoCloseableAction(new Runnable() {
@Override
public void run() {
globalNonThreadSafeUnitOfWork = null;
}
});
}
/**
* Start new unit of work.
*
* @return {@link IUnitOfWork} instance.
*/
public static synchronized IUnitOfWork start() {
return start(null, UnitOfWorkNestingOptions.ReturnExistingOrCreateUnitOfWork);
}
/**
* Start new unit of work.
*
* @param nestingOptions 생성 옵션 {@link UnitOfWorkNestingOptions}
* @return {@link IUnitOfWork} instance.
*/
public static synchronized IUnitOfWork start(UnitOfWorkNestingOptions nestingOptions) {
return start(null, nestingOptions);
}
/**
* Start new unit of work.
*
* @param sessionFactory {@link SessionFactory} instance.
* @return {@link IUnitOfWork} instance.
*/
public static synchronized IUnitOfWork start(SessionFactory sessionFactory) {
return start(sessionFactory, UnitOfWorkNestingOptions.ReturnExistingOrCreateUnitOfWork);
}
/**
* Start new unit of work.
*
* @param sessionFactory {@link SessionFactory} instance.
* @param nestingOptions 생성 옵션 {@link UnitOfWorkNestingOptions}
* @return {@link IUnitOfWork} instance.
*/
public static synchronized IUnitOfWork start(SessionFactory sessionFactory, UnitOfWorkNestingOptions nestingOptions) {
if (log.isDebugEnabled())
log.debug("새로운 UnitOfWork를 시작합니다... sessionFactory=[{}], nestingOptions=[{}]", sessionFactory, nestingOptions);
if (globalNonThreadSafeUnitOfWork != null)
return globalNonThreadSafeUnitOfWork;
IUnitOfWorkImplementor existing = Local.get(IUnitOfWork.CURRENT_UNIT_OF_WORK_KEY, IUnitOfWorkImplementor.class);
boolean useExisting =
existing != null &&
nestingOptions == UnitOfWorkNestingOptions.ReturnExistingOrCreateUnitOfWork;
if (useExisting) {
log.trace("기존 IUnitOfWork 가 존재하므로, 사용횟수만 증가시키고, 기존 IUnitOfWork 인스턴스를 반환합니다. 사용횟수=[{}]", existing.getUsage());
existing.increseUsage();
return existing;
}
log.trace("새로운 IUnitOfWorkFactory 와 IUnitOfWork 를 생성합니다...");
if (existing != null && sessionFactory == null) {
sessionFactory = existing.getSession().getSessionFactory();
} else if (existing == null) {
sessionFactory = getCurrentSessionFactory();
}
setCurrent(getUnitOfWorkFactory().create(sessionFactory, existing));
if (log.isDebugEnabled())
log.debug("새로운 IUnitOfWork를 시작했습니다. sessionFactory=[{}]", sessionFactory);
return getCurrent();
}
/** 현재 실행중인 UnitOfWork를 종료합니다. */
public static synchronized void stop() {
stop(false);
}
/**
* 현재 실행중인 UnitOfWork를 종료합니다.
*
* @param needFlushing Session에 반영된 내용을 flushing 할 것인지 여부
*/
public static synchronized void stop(boolean needFlushing) {
log.trace("현재 실행중인 UnitOfWork를 중지합니다... needFlushing=[{}]", needFlushing);
if (isStarted() && getCurrent() != null) {
if (needFlushing) {
try {
log.trace("현 UnitOfWork의 Session에 대해 flushing 작업을 시작합니다...");
getCurrent().flushSession();
log.trace("현 UnitOfWork의 Session에 대해 flushing 작업을 완료합니다...");
} catch (Exception ignored) {
log.error("UnitOfWork의 Session을 Flushing하는 중 예외가 발생했습니다.", ignored);
}
}
getCurrent().close();
}
setCurrent(null);
log.debug("현재 실행중인 UnitOfWork를 종료했습니다.");
}
/**
* UnitOfWork 내에서 runnable을 수행합니다.
*
* @param runnable 수행할 코드 블럭
*/
public static void run(Runnable runnable) {
shouldNotBeNull(runnable, "runnable");
try (IUnitOfWork unitOfWork = start()) {
runnable.run();
}
}
/**
* UnitOfWork 내에서 runnable을 수행합니다.
*
* @param sessionFactory the session factory
* @param runnable 수행할 코드 블럭
*/
public static void run(SessionFactory sessionFactory, Runnable runnable) {
shouldNotBeNull(runnable, "runnable");
try (IUnitOfWork unitOfWork = start(sessionFactory)) {
runnable.run();
}
}
/**
* UnitOfWork 내에서 runnable을 수행합니다.
*
* @param options Unit of work 생성 옵션
* @param runnable 수행할 코드 블럭
*/
public static void run(UnitOfWorkNestingOptions options, Runnable runnable) {
shouldNotBeNull(runnable, "runnable");
try (IUnitOfWork unitOfWork = start(options)) {
runnable.run();
}
}
/**
* UnitOfWork 내에서 runnable을 수행합니다.
*
* @param sessionFactory the session factory
* @param options Unit of work 생성 옵션
* @param runnable 수행할 코드 블럭
*/
public static void run(SessionFactory sessionFactory, UnitOfWorkNestingOptions options, Runnable runnable) {
shouldNotBeNull(runnable, "runnable");
try (IUnitOfWork unitOfWork = start(sessionFactory, options)) {
runnable.run();
}
}
/**
* Unit of work 를 종료합니다.
*
* @param unitOfWork 닫을 unit of work
*/
public static synchronized void closeUnitOfWork(IUnitOfWork unitOfWork) {
if (log.isDebugEnabled())
log.debug("UnitOfWork를 종료합니다. 종료되는 IUnitOfWork 의 Previous 를 Current UnitOfWork로 교체합니다.");
setCurrent((unitOfWork != null) ? ((IUnitOfWorkImplementor) unitOfWork).getPrevious() : null);
}
/** Close unit of work factory. */
public static synchronized void closeUnitOfWorkFactory() {
log.info("UnitOfWorkFactory를 종료합니다.");
unitOfWorkFactory = null;
}
}