Hibernate LazyInitializationException 해결

LazyInitializationException


Hibernate를 이용하여 개발하다보면, 자주 늘 만나는 녀석이다.
이러한 현상이 발생하는 이유는 다음과 같다.


1. 조회 서비스가 Select를 위한 서비스 (트랜잭션이 걸린)에 조회 요청을 한다.
2. 조회 결과가 반환 되면서 트랜잭션 종료
    - 이때 Entity는 영속상태가 아니라, 준영속 상태로 빠진다.
    - 만약 EAGER 패치로 가져왔다면 Entity의 하위 Entity가 함께 가져왔을 것이다.
      그러나 Lazy로 가져온 경우라면 하위 엔터티는 존재하지 않는 상태임.
3. Lazy 로 가져온 데이터가 준 영속 상태에서 하위 엔터티를 조회하면 LazyInitializationException이 발생한다.


해결방법 :

1. Use Hibernate.initialize :
   Hibernate.initialize 를 이용하여 하위 엔터티를 초기화 한다.
   (여기서 초기화라는 것은 프록시에 해당 엔터티 객체들을 로드해 둔다는 의미이다.)

Hibernate.initialize(topics.getComents());

2. Use Join FETCH
    - JPQL에서 JOIN FETCH문법을 이용하여 한번에 조인 결과를 다 가져온다.
    - EAGER 패치 설정을 기본으로 걸어서 한번에 결과를 가져온다.

3. Use OpenSessionInViewFilter / OpenSessionInViewInteceptor
    - LazyInitializationException은 View에서 보통 많이 발생한다. 이러한 경우 SessionInViewFilter를 걸어서 필터단까지 영속성을 가지고 가거나, 더 짧게는 SessionInViewInterceptor를 걸어 핸들러 단까지만 영속성을 가지고 가는 방법이 있다.

    - 사용시 다음을 주의해야한다.
        - 의도하지 않은 DB변경이 발생하지 않도록 주의해야한다.
        - 성능에 문제를 일으킬 수 있다.

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

# 참고 : 
- 일반적으로 LazyInitializationException은 View를 그릴때 해당 문제가 발생한다.
- 만약 데이터 저장시 발생하는 문제라면, 혹은 서비스단에서 해당 데이터 조합을 미리 가져와서 뷰에대한 데이터를 만들어 줄 수 있다면, 트랜잭션을 Service단까지 올려서 처리하면 된다.

Spring Transactional Propagation

Spring Transactional Propagation

트랜잭션 전파 : 


MANDATORY
현재 트랜잭션을 지원한다. 만약 트랜잭션이 존재하지 않으면 exception을 던진다. 

NESTED
만약 현재 트랜잭션이 존재하는 경우라면, 중첩된 트랜잭션을 실행한다.
트랜잭션이 없다면 PROPAGATION_REQUIRED 처럼 동작한다.
NEVER
트랜잭션 없이 수행되도록 한다. 만약 트랜잭션이 존재한다면 exception을 던진다. 
NOT_SUPPORTED
트랜잭션을 지원하지 않는다.
원래 현재 트랜잭션은 대기한다. 
REQUIRED
현재 트랜잭션을 지원하며, 존재하지 않는다면 신규 트랜잭션을 생성한다. 
REQUIRES_NEW
신규 트랜잭션을 생성한다. 그리고 원래 현재 트랜잭션은 대기한다. 
SUPPORTS
Support a current transaction, execute non-transactionally if none exists.
트랜잭션을 지원한다. 트랜잭션이 존재하지 않았다면 트랜잭션 없이 수행한다. 




Elasticsearch Configuration

Elasticsearch Configuration

Environment Variables 

- EL은 자바의 환경변수 JAVA_OPTS에 따른다.
- 가장 중요한 환경 설정값은 -Xmx (최대 메모리),  -Xms (최소메모리) 의 설정이다.
- 대부분 JAVA_OPTS는 그대로 두고, ES_JAVA_OPTS를 설정하여 JVM의 환경변수를 변경한다.
- ES_HEAP_SIZE 환경 변수는 힙 메모리를 설정하는 값이다.
    - 이 설정값을 설정하면 ES_MIN_MEM(기본 256m), ES_MAX_MEM(기본 1g)을 같은 값으로 설정한다.
# 추천 :
  - 값은 min과 max를 동일하게 하는것
  - mlockall enable로 설정하기.

System Configuration 

File Descriptors

- 머신에서 설정된 open file의 수를 다음으로 증가 시킨다. 32k --> 64k (추천)
- 얼마나 많은 수의 파일이 오픈 될 수 있는지 설정을 한다.
    -Des.max-open-files=true
- 이렇게 설정하면 엘라스틱 서치가 올라올때 최대 오픈할 수 있는 파일 수를 프린트한다.
- 다른 방법으로는 엘라서틱 질의를 통해서 max_file_descriptors를 확인할 수 있다.
curl localhost:9200/_nodes/process?pretty

Virtual memory 

Elasticsearch는 인덱스 저장을 위해서 hybrid mmaps / niofs 디렉토리를 기본으로 사용한다.
  * niofs : 파일시스템에 샤드 인덱스를 NIO를 이용하여 저장한다. 멀티 쓰레드가 동시에 동일한 파일을 읽을 수 있도록 지원한다. Windows에서는 사용하지 않도록 권고한다. Java 구현체에 버그가 있다.
  * mmaps : 파일스스템에 샤드 인덱스를 memory에 파일을 매핑하는 방식으로 저장한다. 메모리 매핑은 파일 크기와 동일한 가상메모리 공간을 이용한다.

기본 OS의 제약은 mmap에 그대로 적용되며, 너무 아래로 떨어지게 되면 out of memory 예외가 발생한다. Linux에서는 다음 명령어를 통해서 제한 값을 올릴 수 있다.
sysctl -w vm.max_map_count=262144
이 값을 영구적으로 설정하기 위해서는 vm.max.map_count 설정을 /etc/sysctl.conf 에 지정하자.
root계정으로만 가능하다.
  * 만약 .deb, .rpm으로 Elasticsearch를 설치한경우라면 이 값은 자동으로 설정된다.

Memory Settings 

대부분의 운영 체제는 시스템 캐시를 위해 가능한 많은 메모리를 사용하고자 한다. 그리고 어플리케이션에서 사용하지 않는 메모리는 swap out 하고자 시도한다. 엘라스틱 서치의 프로세스도 swap이 될 수 있다.
Swapping은 노드의 안정성을 유지하기 위해서 성능을 심각하게 떨어뜨리게 된다. 이러한 swap를 피하는 것이 좋다.

Disable swap 

가장 단순한 옵션으로 swap를 완젼히 정지 시킨다. 일반적으로 EL은 box에서 서비스를 수행하고, ES_HEAP_SIZE에 의해서 컨트롤 된다. swap를 enable로 설정할 필요가 없다.
리눅스 시스템에서 다음 명령어를 통해서 swap를 임시로 끌 수 있다.
sudo swapoff -a 
영구적으로 끄기 위해서는 /etc/fstab를 수정하고, swap로 된 모든 라인을 커멘트 처리 하면 된다.
Windows에서는 다음 경로에서 disable할 수 있다.
System Properties -> Advanced -> Performance -> Advanced -> Virtual memory

Configure swappiness 

두번째 옵션은 sysctl 값인 vm.swappiness를 0으로 설정하는 방법이다.
이것은 커널이 swap 하고자 하는 경향을 줄여주고, 일반적인 환경에서는 swap를 수행하지 않도록 해준다. 긴급 상황에서만 swap를 수행한다.

# 참고 : 커널버젼 3.5-rc1 이상 버젼에서는 swappiness의 값을 0으로 설정하면 OOM killer 가 나타나면 swapping을 수행하는 대신에 해당 프로세스를 kill한다. swappiness 를 1로 설정하면 swapping을 수행한다.

mlockall

이 옵션은 mlockall을 이용하며  Linux / Unix시스템의 기능을 이용한다.  혹은 윈도우에서는 VirtualLock 기능을 이용한다.
이것은 RAM의 공간에 lock을 거는 방법으로 Elasticsearch 메모리가 swapped out되는 것을 막아준다. 이것은 config/elasticsearch.yml 파일에 다음과 같이 정의하는 것으로 설정이 가능하다.
bootstrap.mlockall: true
Elasticsearch가 실행된후 해당 옵션이 성공적으로 적용되었는지 확인은 다음과 같이 수행할 수 있다.
curl http://localhost:9200/_nodes/process?pretty
만약 mlockall이 false로 설정 되어 있다면 mlockall요청이 실패 했음을 의미한다. 이 이유는 Linux/Unix시스템에서 EL이 메모리 lock권한을 가지고 있지 않은 경우 발생한다.
이것은 ulimit -l unlimited 값을 root권한으로 지정하면 가능하다.
mlockall이 실패할 수 있는 또 다른 이유는 /tmp 디렉토리가 noexec옵션으로 마운트 된 경우에 가능성이 있다. 이것은 Elasticsearch를 실행할때 옵션을 선택하여 temp 위치를 변경하는 것으로 해결이 가능하다.
./bin/elasticsearch -Djna.tmpdir=/path/to/new/dir
# mlockall은 JVM이나 shell session을 종료시킬 수 있는데 이것은 가용한 용량보다 더 많은 메모리를 할당하고자 하는 경우 발생될 수 있다.

Elasticsearch Settings

elasticsearch설정 파일은 ES_HOME/config 폴더 아래에 있다. 폴더는 2개의 파일이 있으며, elasticsearch.yml로 Elasticsearch 설정을 하기 위한 파일과, logging.yml로 로기을 설정하기 위함이다.

설정 포맷은 YAML로 되어 있다.

다음은 모든 Base module들의 네트워크를 변경하는 것으로 바인드 시키고 publish를 해준다.
network :
    host : 10.0.0.4

 Paths

사용 환경에서 데이터의 위치와 로그 파일의 위치를 지정하고자 하는 니즈가 있을 것이다.
path :
    logs: /var/log/elasticsearch
    data: /var/data/elasticsearch

Cluster name

실제 환경에서는 절대로 클러스터 이름을 지정하는 것을 빼먹지 말자. 이것은 discover과 auto-join을 위해서 꼭 필요하다.
cluster:
    name: <NAME OF YOUR CLUSTER>
서로다른 환경에서 동일 클러스터 이름을 사용하지 않도록 주의하자. 그렇지 않으면 잘못된 클러스터에 노드가 조인된다. 예를 들면 logging-dev, logging-stage, logging-prod와 같이 클러스터를 나누자.

Node name

기본 노드 이름을 변경하고자 할수도 있다. 각 노드는 노출되는 호스트의 이름이 된다. 기본적으로 Elasticsearch는 랜덤하게 지정된다.
node:
    name: <NAME OF YOUT NODE>
머신의 호스트 이름은 환경 변수 HOSTNAME에 지정된 내역을 따른다. 만약 머신에 하나의 엘라스틱 서치 노드를 수행시킨다면 ${...}을 이용하여 호스트 이름을 지정할 수 있다.
node:
    name: ${HOSTNAME}
내부적으로 모든 설정은 namespaced 구조로 설정된다.
예를 들어 이전에 설명한 node.name으로 지정하여 설정할 수 있다. 이 의미는 설정을 JSON형식으로 쉽게 지정할 수 있다. 만약 JSON형식으로 처리하고자 한다면 elasticsearch.yml을 elasticsearch.json으로 변경하면 된다.

Configuration styles

{
    "network" : {
        "host" : "10.0.0.4"
    }
}
이것은 외부 설정인 ES_JAVA_OPTS를 이용하여 설정할 수 있다.
elasticsearch -Des.network.host=10.0.0.4
다른 옵션은 es.default 이다. prefix가 es.이며 이는 기본 설정을 의미한다. 이값은 명시적으로 설정파일에 지정하지 않으면 기본값으로 설정된다는 의미이다.

다른 옵션은 ${...}을 이용하는 것으로 이것은 환경 변수로 설정된 값을 이용한다.
{
    "network" : {
        "host" : "${ES_NET_HOST}"
    }
}
추가적으로 설정파일에 내역을 저장하지 않고자 한다면 ${prompt.text}혹은 ${prompt.secret} 을 이용할 수 있다. 이는 foreground로 설정값을 입력하라고 묻게 된다.
${prompt.secret} 는 터미널로 입력 하도록 물어본다.
node:
    name: ${prompt.text}
elasticsearch 커맨드를 실행하면 다음과 같은 프롬프트 화면을 보게 된다.
Enter value for [node.name]
# Elasticsearch는 ${prompt.text}혹은 ${prompt.secret}을 설정했을때 백그라운드로 실행하면 elasticsearch는 정상적으로 시작되지 않는다.

Index Settings

인덱스들이 생성되면 각 자신들의 설정을 제공할 수 있다. 예를 들어 다음은 인덱스를 메모리 기반의 저장소로 생성한다. 파일시스템을 생성하지 않고 말이다. 포맷은 YAML이나 JSON으로 지정이 가능하다.
$ curl -XPUT http://localhost:9200/kimchy/ -d \
'
index:
    refresh_interval: 5s
'
인덱스 레벨 설정은 node레벨과 같이 설정이 가능하다. 예를 들어 elasticsearch.yml파일에서 다음과 같이 지정할 수 있다.
index :
    refresh_interval: 5s
이것이 의미하는 바는 각 인덱스는 실행된 특정 노드에서 생성된 것을 획득하며, 이것은 시작시 특별히 지정하지 않으면 설정에 따라 메모리에 인덱스를 저장하게 될 것이다. 즉, 인덱스 레벨의 설정은 노드 설정에 지정된 값을 오버라이딩 한다. 물론 상단 예는 설정의 일부분이다.
$ elasticsearch -Des.index.refresh_interval=5s
모든 인덱스 레벨의 설정값은 각 인덱스 모듈에서 찾을 수 있다.

Loggin 

Elasticsearch는 내부 로깅을 위한 추상화를 제공한다. log4j를 이용한다. 이것은 log4j 설정을 통해서 가능하며 YAML을 이용하여 설정한다. 그리고 로깅 설정 파일은 config/logging.yml 에 지정이 가능하다.
JSON과 properties 포맷도 지정이 가능하다. 로깅 설정 파일은 여러개를 로드 할 수 있다. 이들은 시작시에 하나로 머지된다.
logger 섹션은 자바 패키지를 포함하고 있다. 그리고 각 해당하는 로그 레벨을 지정을 할 수 있다. 이것은 org.elasticsearch 프리픽스를 사용하지 않아도 되도록 한다.
appender 섹션은 로그들의 목적지를 포함한다.

Deprecation logging

추가적으로 elasticsearch는 deprecated된 액션의 로깅을 가능하게 한다. 예를 들어 앞으로 특정 기능을 이관할 필요가 있을때 미리 결정할 수 있도록 해준다. 기본적으로 deprecation로깅은 disabled되어 있다. config/logging.yml파일 에서 DEBUG로 로그 레벨을 설정할 수 있다.
deprecation: DEBUG, deprecation_log_file
이것은 일별 롤링 로그 파일을 로그 디렉토리에 생성한다.

from : https://www.elastic.co/guide/en/elasticsearch/reference/2.1/setup-configuration.html












SpringBoot에서 Log설정하기.

SpringBoot에서 Log설정하기.

SpringBoot에서 Log 설정하기. 

Spring Boot에서 로깅을 위해서는 spring-boot-starter-logging을 의존성에 추가하면 된다.
웹 어플리케이션을 사용한다면, spring-boot-starter-web만 사용하면 된다.

Maven 의존성 추가하기 :
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

application.properties 설정하기. 
logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate=ERROR
미세한 설정을 위해서는 classpath:logback.xml을 설정할 수 있다. 





Mac Mysql 설치

Mac Mysql 설치

MYSql Mac 설치 & 삭제 


1. Mac 에서 mysql 삭제 명령어 : 

sudo rm /usr/local/mysql
sudo rm -rf /usr/local/mysql*
sudo rm -rf /Library/StartupItems/MySQLCOM
sudo rm -rf /Library/PreferencePanes/My*
rm -rf ~/Library/PreferencePanes/My*
sudo rm -rf /Library/Receipts/mysql*
sudo rm -rf /Library/Receipts/MySQL*
sudo rm -rf /var/db/receipts/com.mysql.*
sudo vi /etc/hostconfig

2. 초기 비번 변경하기 : 

mysql> use mysql;
ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.

상기 메시지가 나타나는 경우 .

mysql> SET PASSWORD = PASSWORD('NEW_PASSWORD');

* 상기 명령어는 다음과 같이 처음 사용자를 생성하거나, 사용자 비번 변경을 요청을 다음과 했을경우 나타난다.

ALTER USER 'jeffrey'@'localhost' PASSWORD EXPIRE;

3. 데이터베이스 목록 보기

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

4. 데이터베이스 생성 및 선택하기 

mysql> create database boards;Query OK, 1 row affected (0.00 sec)
mysql> show databases;+--------------------+
| Database           |
+--------------------+
| information_schema |
| boards             |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)
mysql> use boards;Database changed
5. 데이터베이스 권한을 사용자에게 부여하기. 
mysql> create user test;Query OK, 0 rows affected (0.00 sec) 
mysql> grant all privileges on boards to test@localhost identified by 'test';Query OK, 0 rows affected, 1 warning (0.00 sec) 
mysql> flush privileges; Query OK, 0 rows affected (0.00 sec)

6. 부여된 권환 확인하기. 
mysql> show grants for test@localhost;
+----------------------------------------------------------------+
| Grants for test@localhost                                      |
+----------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'test'@'localhost'                       |
| GRANT ALL PRIVILEGES ON `mysql`.`boards` TO 'test'@'localhost' |
+----------------------------------------------------------------+
2 rows in set (0.00 sec)

7. 권한 뺏기 .
mysql> revoke all on boards from test@localhost;

8. 계정 삭제하기. 

mysql> drop user test@localhost;

9. 패스워드초기화 
$sudo /usr/sbin/mysqld --skip-grant-tables --skip-networking &
$sudo mysqld -proot mysqld
$mysql -u root mysql
$mysql> UPDATE user SET Password=PASSWORD('root_password') where USER='root';
$mysql> FLUSH PRIVILEGES;

Lombok 설치하기 (Mac + Eclipse)

Mac에서 이클립스를 이용하여 Lombok 설치하고 사용하기. 


Lombok은 getter/setter/constructor/toString/hash/equals등을 자동으로 생성해주는 멋진 툴이다.
그 사용법을 알아보자.

1. Lombok 다운로드 하기. 
https://projectlombok.org/download.html

lombok.jar 1.16.6을 다운받자.

2. Lombok 실행하여  Eclipse와 연동하기. 
준비사항 :
- JDK 최신버젼
- Eclipse 최신버젼

다운 받은 lombok.jar 파일 위치에서 다음과 같이 실행하자.
이전 준비 사항들이 이미 Mac에 설치 되어 있다는 가정하에 진행하겠다.
java -jar lombok.jar
그러면 다음 화면이 나온다.

위 내용은 라는 의미이다.
IDE를 찾을 수 없다.
만약 IDE가 컴퓨터에 이미 설치되어 있다면 'Specify Location...' 버튼을 눌러서 수동으로 IDE가 설치된 경로를 지정해달라. 
"확인"을 누르고
"Specify location..." 버튼을 클릭하자.


이클립스 경로에서 STS.ini를 찾아서 "Open"을 클릭하자.

"Install / Update" 를 클릭하여 Eclipse에 설치하자.
-startup
../Eclipse/plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
../Eclipse/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.1.300.v20150602-1417
-product
org.springsource.sts.ide
--launcher.defaultAction
openFile
-vmargs
-Dosgi.requiredJavaVersion=1.7
-Xms2048m
-XX:MaxPermSize=512m
-Xverify:none
-XstartOnFirstThread
-Dorg.eclipse.swt.internal.carbon.smallFonts
-Xdock:icon=../Resources/sts.icns
-Xmx2048m
-javaagent:../Eclipse/lombok.jar
마지막에 lombok.jar가 설치 된 것을 확인할 수 있다. 


maven Depencency 추가하기 : 
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.8</version><scope>provided</scope></dependency>


이제 준비가 되었다. 이제는 이클립스를 다시 실행하여 직접 테스트를 해보면 된다.

3. Lombok 이용하여 boilerplate code 제거하기. 

Lombok features

val
final local 변수를 써야하는 번거로운 작업을 없애준다.

@NonNull
NullPointerException을 발생하지 않도록 미리 체크한다.

@Cleanup
자동 리소스 관리를 수행한다.: 안전하게 close()메소드를 호출한다.

@Getter / @Setter
getter/setter메소드를 자동으로 생성한다.

@ToString
lombok가 자동으로 toString 메소드를 생성해준다. 모든 필드들을 toString으로 노출시켜준다.

@EqualsAndHashCode
equals와 hashCode메소드를 자동으로 생성해준다. 객체의 필드들을 이용하여 구현하게 된다.

@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor
생성자를 만들어 준다.
@NoArgsConstructor : 파라미터 없는 생성자를 만들어준다.
@RequiredArgsConstructor : not null필드나 final필드들을 아규먼트로 하는 생성자를 만든다.
@AllArgsConstructor : 모든 파라미터를 이용하여 생성자를 만든다.

@Data
@ToString, @EqualsAndHashCode, @Getter, @Setter, @RequiredArgsConstructor 를 모두 한번에 적용한 어노테이션이다.

@Value
불변 클래스들을 매우 쉽게 만든다.

@Builder
객체 생성을 빌더로 만들어준다.

@SneakyThrows
checked exceptions를 던진다. 이전에 thrown을 지정하지 않은 경우에 설정된다.

@Synchronized
synchronized가 올바르게 수행되도록 한다. locks를 볼 수 없을 것이다.

@Getter(lazy=true)
Getter을 생성한다. lazy하게.. 느린것이 좋은 것이다.

@Log
Logs를 사용할 수 있도록 해준다.

더 상세한 가이드를 보고자 한다면 다음 경로에서 확인해보자.
http://jnb.ociweb.com/jnb/jnbJan2010.html

4. Sample 예제 

import lombok.Data;

public @Data class Hello {
    int id;
    String name;
}

@Data를 이용하여 한번에 적용해 보았다. 

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@NoArgsConstructor
@ToString
public class Person {

@Getter
long personId;

@Getter @Setter
String name;

public static void main(String[] args) {
System.out.println("TEST : " + new Person());
}
}
@NoArgsConstructor을 이용해서 파라미터 없는 기본 생성자를 생성 시켜 보았다.
@ToString 을 이용하면 상기 모든 필드에 대해서 스트링으로 노출할 것이다.

toString을 출력해보면 다음과 같다.

TEST : Person(personId=0, name=null)