Web/JPA 2019. 1. 15. 22:04

JPA 영속성 관리 - persistence context(영속성컨텍스트)


JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계부분과 매핑한 엔티티를 실제 사용하는 부분으로 나눌 수 있다.


엔티티 매니저 팩토리와 엔티티 매니저


EntityManager는 엔티티를 저장하고, 수정하고, 삭제하고, 조회하는 등 엔티티와 관련된 모든 일을 처리한다. 이름 그대로 엔티티를 관리하는 관리자다. 개발자 입장에서 엔티티 매니저는 엔티티를 저장하는 가상의 데이터베이스로 생각하면 될 듯 싶다.


그러면 엔티티 매니저 팩토리란 무엇인가? 바로 이 엔티티 매니저를 생성해주는 공장역할을 하는 객체이다. 보통 엔티티 매니저 팩토리는 생성비용이 크게 때문에 하나만 생성해서 모든 스레드가 공유할 수 있도록 설계되었고, 엔티티 매니저는 사용할 때마다 새로 생성하면 된다. 즉, 엔티티 매니저는 데이터베이스 커넥션과 관련이 있는 객체이기에 스레드간 공유가 되서는 안되는 객체이다. 이 엔티티 매니저는 데이터베이스와 커넥션을 맺지만 꼭 필요할 때 커넥션을 맺는다.(트랜잭션이 시작되었을 경우) 


Ex) EntityManagerFactory emf = Persistence.createEntityManagerFactory("persist"); -> "persist"는 persistence.xml에 생성한 persistence unit 명이다.

      EntityManager em = ema.createEntityManager();





영속성 컨텍스트란?


영속성 컨텍스트란 JPA에서 가장 중요한 용어 중 하나이다. 이것을 한글로 표현하면 "엔티티를 영구 저장하는 환경"이라고 표현해도 좋을 것 같다. 즉, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.


em.persist(entity)는 실제로 데이터베이스에 회원 엔티티를 저장하는 것이 아니고, 엔티티 매니저를 사용해 엔티티를 영속성 컨텍스트에 저장하는 것이다.



엔티티의 생명주기


비영속 : 영속성 컨텍스트와 전혀 관계가 없는 상태이다.

영속: 영속성 컨테스트에 저장된 상태

준영속: 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제: 삭제된 상태



비영속 상태를 예를 들면 엔티티 객체를 생성한 상태이다.


Member m = new Member(); -> 지금 이 상태는 순수한 객체 상태이며, 아직 저장하지 않은 것이라서 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 것이다.


영속 상태를 예를 들면 em.persist(entity) 혹은 em.find(entity) 등을 호출한 상태이며, 이 상태는 영속성 컨테스트가 관리하는 엔티티가 되었다 라는 뜻이다. 


준영속 상태는 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태를 말한다. 예를 들면, em.detach(),em.close(),em.clear() 등 분리,닫기,초기화 등의 메소드를 호출하게 되면 영속된 엔티티들은 모두 준영속 상태가 된다.


삭제 상태는 말그대로 영속성 컨텍스트(+데이터베이스)에서 상태가 되는 것이다. em.remove(entity)




영속성 컨텍스트의 특징


1)영속성 컨텍스트와 식별자 값 - 영속성 컨텍스트는 엔티티를 식별자 값(@Id로 테이블의 기본키와 매핑한 값)으로 구분한다. 즉, 영속상태에 있으려면 반드시 식별자 값이 존재해야한다. 만약 식별자 값이 없다면 예외가 발생하게 된다.


2)JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는 데, 이것을 Flush라고 한다.


영속성 컨텍스트가 엔티티를 관리하면서의 장점

-1차 캐시 : persist메소드를 호출하면 commit이 호출되기 전까지 1차 캐시에 저장된다. 즉, find 메소드를 호출하면 데이터베이스에서 직접 엑세스해서 가져오는 것이 아니라, 메모리의 캐시에서 엔티티를 가져오게 되므로 빠른 속도와 적은 비용으로 조회가 가능하다.


-동일성 보장 : 아무리 같은 엔티티 키값으로 여러번 조회를 해도 1차 캐시에서 하나의 객체만 리턴해주기 때문에 항상 동일성을 보장한다.(같은 인스턴스를 가르킨다.)


-트랜잭션을 지원하는 쓰기 지연 : JPA는 commit 호출전까지 데이터베이스에 쓰기를 하지 않는다. 그말은 쿼리를 쿼리 저장소에 차곡차곡 모아두다가 commit이 되는 시점에 모아둔 모든 쿼리를 데이터베이스에 반영하는 지연작업을 한다.


-변경 감지:ex) Member m = em.find(Member.class,"1");

                       m.setAge(27);


JPA는 이렇게 set메소드로 데이터만 변경했을 뿐이지만 commit 시점에 데이터베이스의 데이터가 update된다. 왜냐? 영속성 컨텍스트에는 항상 엔티티의 복사본인 스냅샵을 가지고 있기 때문에 commit 시점에 스냅샵과 현재 엔티티의 값을 비교해 다른 점이 있다면 update쿼리를 쿼리저장소에 보내서 commit될때 데이터베이스에 반영이 된다. 하지만 가정이 있다. 반드시 영속상태의 엔티티만 적용이 된다는 것이다.

업데이트 쿼리는 모든 필드를 대상으로 업데이트를 한다.(수정 쿼리가 항상 같기에 애플리케이션 로딩시점에 수정 쿼리를 미리 생성해두고 재사용 할 수 있고, 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한번 파싱된 쿼리를 재사용한다.) 물론 변경된 대상으로만 update쿼리를 보내는 방법도 있는데 이것은 hibernate 확장기능을 이용해야한다(DynamicUpdate,DynamicInsert..)

-지연 로딩



posted by 여성게
:

https://tourspace.tistory.com/3?category=788398


위의 포스트된 글 일부에 스트레티지 패턴이 설명되어있으며, 예제코드도 나와있음.



posted by 여성게
:

java - synchronized 란? 사용법?




멀티스레드를 사용하면 프로그램적으로 좋은 성능을 있지만,

멀티스레드 환경에서 반드시 고려해야할 점인 스레드간 동기화라는 문제는 해결해야합니다.

 

예를 들어 스레드간 서로 공유하고 수정할 있는 data 있는데 스레드간 동기화가 되지 않은 상태에서 

멀티스레드 프로그램을 돌리면, data 안정성과 신뢰성을 보장할 없습니다.

 

따라서 data thread-safe 하기 위해 자바에서는 synchronized 키워드를 제공해 스레드간 동기화를 시켜 data thread-safe 가능케합니다.

 

자바에서 지원하느 Synchronized 키워드는 여러개의 스레드가 한개의 자원을 사용하고자 ,

현재 데이터를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은 데이터에 접근 없도록 막는 개념입니다.

 

Synchronized 키워드는 변수와 함수에 사용해서 동기화 있습니다.

하지만 Synchronized 키워드를 너무 남발하면 오히려 프로그램 성능저하를 일으킬 있습니다.

 

이윤 Synchronized 키워드를 사용하면 자바 내부적으로 메서드나 변수에 동기화를 하기 위해 block unblock 처리하게 되는데 

이런 처리들이 만약 너무 많아지게 되면 오히려 프로그램 성능저하를 일으킬수 있는 것입니다

(block unblock 프로그램 내부적으로 어느정도  공수가 들어가는 작업입니다.)

 

따라서 적재적소에 Synchronized 키워드를 사용하는 것이 중요합니다!

 

// 1. 메서드에서 사용하는 경우

public synchronized void method(){// 코드}

 

 

// 2. 객체 변수에 사용하는 경우(block)

private Object obj = new Object();

public void exampleMethod(){ synchronized(obj){//코드 }}

 

 

 

 

 

  1. public class ThreadSynchronizedTest {
  2.  
  3.     public static void main(String[] args) {
  4.     // TODO Auto-generated method stub
  5.         Task task = new Task();
  6.             Thread t1 = new Thread(task);
  7.             Thread t2 = new Thread(task);
  8.             </p><p>
  9.             t1.setName("t1-Thread");
  10.             t2.setName("t2-Thread");
  11.              
  12.             t1.start();
  13.             t2.start();
  14.     }
  15.  
  16. }
  17.  
  18. class Account{
  19.     int balance = 1000;
  20.       
  21.     public void withDraw(int money){
  22.      
  23.         if(balance >= money){
  24.             try{
  25.                 Thread thread = Thread.currentThread();
  26.                 System.out.println(thread.getName() + " 출금 금액 ->> "+money);
  27.                 Thread.sleep(1000);
  28.                 balance -= money;
  29.                 System.out.println(thread.getName()+" balance:" + balance);
  30.                  
  31.             }catch (Exception e) {}
  32.        
  33.         }
  34.     }
  35. }
  36.   
  37. class Task implements Runnable{
  38.     Account acc = new Account();
  39.       
  40.     @Override
  41.     public void run() {
  42.         while(acc.balance > 0){
  43.             // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
  44.             int money = (int)(Math.random() * 3 + 1) * 100;
  45.             acc.withDraw(money);
  46.        
  47.         }
  48.     }
  49. }

 

예제에 대한 설명을 하면

 

Account 라는 클래스에는 balance 잔액과 잔액을 삭감시키는 인출메서드가 있습니다.

 

지난시간 스레드를 만들때 Runnable 인터페이스를 구현하여 Task 만들고 Task Thread 생성시 생성자에 매개변수로 넣으면

우리가 만든 스레드가 Task 정의되어있는 대로 일을 실행한다고 했습니다.

 

Runnable 구현하여 만든 Task 100,200,300 랜덤하게 값을 전달받아 moneny 변수에 할당하고 금액만큼 Account 인스턴스의 인출메서드를 호출해

 balance (잔액) 출금시키는 일을 구현해놨습니다.

 

다음 main 메서드에서 스레드 t1, t2 만들고 각각의 스레드 이름을 정의합니다.

t1, t2 스레드가 시작하면 잔액이 0 때까지 스레드가 경쟁하며 출금시킬 것입니다.

 

 

결과화면

 

 

결과를 보면 분명 잔액이 0 될때까지 출금을 하라고 햇는데 잔액이 마이너스가 됬습니다.

여기서 멀티스레드의 문제점이 발견됩니다. balance(잔액) thread-safe 되지 않았기 때문에 t1 스레드가 잔액을 삭감하기 전에 

t2 balance(잔액) 접근해 삭감을 해버리고 다시 t1 slee()에서 깨어나 balance(잔액) 삭감해버리기 때문에

 

잔액이 0 이하의 마이너스 값을 가지게 됩니다.

 

해결하는 방법은 간단합니다

공유데이터에 대한 접근과 수정이 이루어지는 메서드에 synchronized 키워드를 리턴타입 앞에 붙여주면 됩니다

t1스레드가 먼저 공유데이터나 메서드에 점유하고 있는 상태인 경우 block으로 처리하기 때문에 t1 이외의 스레드의 접근을 막습니다

 

t1스레드가 작업을 끝냈다면 .unblock으로 처리하여 t1 이외의 스레드의 접근을 허락합니다.

 

 

 

 

 

synchronized 키워드로 멀티스레드 동기화 처리

 
  1. public class ThreadSynchronizedTest {
  2.  
  3.     public static void main(String[] args) {
  4.         // TODO Auto-generated method stub
  5.            Task task = new Task();
  6.             Thread t1 = new Thread(task);
  7.             Thread t2 = new Thread(task);
  8.              
  9.             t1.setName("t1-Thread");
  10.             t2.setName("t2-Thread");
  11.              
  12.             t1.start();
  13.             t2.start();
  14.     }
  15.  
  16. }
  17.  
  18. class Account{
  19.     int balance = 1000;
  20.       
  21.     public synchronized void withDraw(int money){
  22.      
  23.         if(balance >= money){
  24.             try{
  25.                 Thread thread = Thread.currentThread();
  26.                 System.out.println(thread.getName() + " 출금 금액 ->> "+money);
  27.                 Thread.sleep(1000);
  28.                 balance -= money;
  29.                 System.out.println(thread.getName()+" balance:" + balance);
  30.                  
  31.             }catch (Exception e) {}
  32.        
  33.         }
  34.     }
  35. }
  36.   
  37. class Task implements Runnable{
  38.     Account acc = new Account();
  39.       
  40.     @Override
  41.     public void run() {
  42.         while(acc.balance > 0){
  43.             // 100, 200, 300 중의 한 값을 임의로 선택해서 출금(withDraw)한다.
  44.             int money = (int)(Math.random() * 3 + 1) * 100;
  45.             acc.withDraw(money);
  46.        
  47.         }
  48.     }
  49. }

결과화면

 

 

 

synchronized 키워드를 사용함으로써 balance 공유데이터에 대한 thread-safe를 시켰기 때문에

데이터나 메서드 점유하고 있는 스레드가 온전히 자신의 작업을 마칠 수 있습니다.


다른 블로그에 좋은 글이 있어서 펌해봅니다
메소드 레벨의 동기화는 해당 인스턴스 자체로 락을 걸어버린다.

자바 동기화 블록은 메소드나 블록 코드에 동기화 영역을 표시하며 자바에서 경합 조건을 피하기 위한 방법으로 쓰인다.

 

 

 

자바 synchronized 키워드

 

 자바 코드에서 동기화 영역은 synchronizred 키워드로 표시된다. 동기화는 객체에 대한 동기화로 이루어지는데(synchronized on some object), 같은 객체에 대한 모든 동기화 블록은 한 시점에 오직 한 쓰레드만이 블록 안으로 접근하도록 - 실행하도록 - 한다. 블록에 접근을 시도하는 다른 쓰레드들은 블록 안의 쓰레드가 실행을 마치고 블록을 벗어날 때까지 블록(blocked) 상태가 된다.

 

synchronized 키워드는 다음 네 가지 유형의 블록에 쓰인다.

인스턴스 메소드

스태틱 메소드

인스턴스 메소드 코드블록

스태틱 메소드 코드블록

 

어떤 동기화 블록이 필요한지는 구체적인 상황에 따라 달라진다.

 

 

인스턴스 메소드 동기화

 

다음은 동기화 처리된 인스턴스 메소드이다.

 

public synchronized void add(int value){

      this.count += value;

}

 

메소드 선언문의 synchronized 키워드를 보자. 이 키워드의 존재가 이 메소드의 동기화를 의미한다.

 

인스턴스 메소드의 동기화는 이 메소드를 가진 인스턴스(객체)를 기준으로 이루어진다. 그러므로, 한 클래스가 동기화된 인스턴스 메소드를 가진다면, 여기서 동기화는 이 클래스의 한 인스턴스를 기준으로 이루어진다. 그리고 한 시점에 오직 하나의 쓰레드만이 동기화된 인스턴스 메소드를 실행할 수 있다. 결국, 만일 둘 이상의 인스턴스가 있다면, 한 시점에, 한 인스턴스에, 한 쓰레드만 이 메소드를 실행할 수 있다. 

 

인스턴스 당 한 쓰레드이다. 

 

 

스태틱 메소드 동기화

 

스태틱 메소드의 동기화는 인스턴스 메소드와 같은 방식으로 이루어진다.

 

public static synchronized void add(int value){

      count += value;

}

역시 선언문의 synchronized 키워드가 이 메소드의 동기화를 의미한다.

 

스태틱 메소드 동기화는 이 메소드를 가진 클래스의 클래스 객체를 기준으로 이루어진다. JVM 안에 클래스 객체는 클래스 당 하나만 존재할 수 있으므로, 같은 클래스에 대해서는 오직 한 쓰레드만 동기화된 스태틱 메소드를 실행할 수 있다.

 

만일 동기화된 스태틱 메소드가 다른 클래스에 각각 존재한다면, 쓰레드는 각 클래스의 메소드를 실행할 수 있다.

 

클래스 -쓰레드가 어떤 스태틱 메소드를 실행했든 상관없이- 당 한 쓰레드이다.

 

 

인스턴스 메소드 안의 동기화 블록

 

동기화가 반드시 메소드 전체에 대해 이루어져야 하는 것은 아니다. 종종 메소드의 특정 부분에 대해서만 동기화하는 편이 효율적인 경우가 있다. 이럴 때는 메소드 안에 동기화 블록을 만들 수 있다.

 

public void add(int value){

 

    synchronized(this){

       this.count += value;   

    }

  }

 

 

이렇게 메소드 안에 동기화 블록을 따로 작성할 수 있다. 메소드 안에서도 이 블록 안의 코드만 동기화하지만, 이 예제에서는 메소드 안의 동기화 블록 밖에 어떤 다른 코드가 존재하지 않으므로, 동기화 블록은 메소드 선언부에 synchronized 를 사용한 것과 같은 기능을 한다.

 

동기화 블록이 괄호 안에 한 객체를 전달받고 있음에 주목하자. 예제에서는 'this' 가 사용되었다. 이는 이 add() 메소드가 호출된 객체를 의미한다. 이 동기화 블록 안에 전달된 객체를 모니터 객체(a monitor object) 라 한다. 이 코드는 이 모니터 객체를 기준으로 동기화가 이루어짐을 나타내고 있다. 동기화된 인스턴스 메소드는 자신(메소드)을 내부에 가지고 있는 객체를 모니터 객체로 사용한다.

 

같은 모니터 객체를 기준으로 동기화된 블록 안의 코드를 오직 한 쓰레드만이 실행할 수 있다.

 

다음 예제의 동기화는 동일한 기능을 수행한다.

 

 public class MyClass {

  

    public synchronized void log1(String msg1, String msg2){

       log.writeln(msg1);

       log.writeln(msg2);

    }

 

  

    public void log2(String msg1, String msg2){

       synchronized(this){

          log.writeln(msg1);

          log.writeln(msg2);

       }

    }

  }

 

한 쓰레드는 한 시점에 두 동기화된 코드 중 하나만을 실행할 수 있다. 여기서 두 번째 동기화 블록의 괄호에 this 대신 다른 객체를 전달한다면, 쓰레드는 한 시점에 각 메소드를 실행할 수 있다. -동기화 기준이 달라지므로.

 

 

스태틱 메소드 안의 동기화 블록

 

다음 예제는 스태틱 메소드에 대한 것이다. 두 메소드는 각 메소드를 가지고 있는 클래스 객체를 동기화 기준으로 잡는다.

 

 public class MyClass {

 

    public static synchronized void log1(String msg1, String msg2){

       log.writeln(msg1);

       log.writeln(msg2);

    }

 

  

    public static void log2(String msg1, String msg2){

       synchronized(MyClass.class){

          log.writeln(msg1);

          log.writeln(msg2);  

       }

    }

  }

 

 

같은 시점에 오직 한 쓰레드만 이 두 메소드 중 어느 쪽이든 실행 가능하다. 두 번째 동기화 블록의 괄호에 MyClass.class 가 아닌 다른 객체를 전달한다면, 쓰레드는 동시에 각 메소드를 실행할 수 있다.

 

 


▶︎▶︎▶︎박철우님의 블로그

 

 

posted by 여성게
:
Search-Engine/Lucene 2019. 1. 3. 21:07

Lucene library를 이용한 간단한 색인/검색(루씬 라이브러리이용)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.lucene.study;
 
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
 
//yeoseong_yoon,2019/1/2->txt파일을 색인하는 유틸클래스
public class Indexer {
    
    private IndexWriter writer;
    
    //루씬의 IndexWriter 생성
    public Indexer(String indexDir) throws IOException {
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        writer = new IndexWriter(dir,new IndexWriterConfig(new StandardAnalyzer()));
    }
    
    public void close() throws IOException {
        writer.close();
    }
    
    public int index(String dataDir,FileFilter filter) throws IOException {
        
        File[] files = new File(dataDir).listFiles();
        
        for(File f : files) {
            if(!f.isDirectory() &&
                    !f.isHidden() &&
                    f.exists() &&
                    f.canRead() &&
                    (filter == null || filter.accept(f))) {
                
                indexFile(f);
            }
        }
        
        //색인된 문서건수 리턴
        return writer.numDocs();
    }
    
    private void indexFile(File file) throws IOException {
        System.out.println("Indexing "+file.getCanonicalPath());
        Document doc = getDocument(file);
        writer.addDocument(doc);
    }
    
    @SuppressWarnings("deprecation")
    protected Document getDocument(File file) throws IOException {
        Document doc = new Document();
        //파일의 내용추가
        doc.add(new Field("content"new FileReader(file)));
        //파일 이름추가
        doc.add(new Field("filename",file.getName(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        //파일 전체경로 추가
        doc.add(new Field("fullpath",file.getCanonicalPath(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        
        return doc;
    }
    
    //FileFilter를 이용하여 해당 확장자만 걸러낸다.
    private static class TextFilesFilter implements FileFilter{
        public boolean accept(File path) {
            return path.getName().toLowerCase().endsWith(".txt");
        }
    }
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        if(args.length!=2) {
            throw new IllegalArgumentException("please input index dir & data dir");
        }
        
        String indexDir = args[0];
        String dataDir = args[1];
        
        long start = System.currentTimeMillis();
        Indexer indexer = new Indexer(indexDir);
        int numIndexed = 0;
        
        try {
            numIndexed = indexer.index(dataDir, new TextFilesFilter());
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }finally {
            indexer.close();
        }
        
        long end = System.currentTimeMillis();
        
        System.out.println("Indexing " + numIndexed + " files took " + (end-start) + " milliseconds");
    }
 
}
 
cs


=>특정 디렉토리에 들어있는 txt파일을 특정 디렉토리 위치에 색인하는 코드이다.(실행시 arguments로 2개의 인자를 전달해야한다.)




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.lucene.study;
 
import java.io.File;
import java.io.IOException;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
 
public class Searcher {
    
    public static void search(String indexDir, String query) throws IOException, ParseException {
        
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        //색인을 연다.
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(dir));
        
        //질의를 분석한다.
        QueryParser parser = new QueryParser("content",
                                            new StandardAnalyzer());
        
        Query q = parser.parse(query);
        
        long start = System.currentTimeMillis();
        
        TopDocs hits = indexSearcher.search(q, 10);
        
        long end = System.currentTimeMillis();
        
        System.out.println("Found "+ hits.totalHits +" document(s) (in "+(end-start) 
                +" millisecond) that matched query '" + q + "' :");
        
        for(ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = indexSearcher.doc(scoreDoc.doc);
            System.out.println(String.format("content - %s\nfullpath = %s"
                                            ,doc.get("content"
                                            ,doc.get("fullpath")));
        }
        
    }
 
    public static void main(String[] args) throws IOException, ParseException {
        // TODO Auto-generated method stub
        if(args.length !=2) {
            throw new IllegalArgumentException("please input index dir & search query String");
        }
        
        String indexDir = args[0];
        String query = args[1];
        search(indexDir, query);
    }
 
}
 
cs

=>위에서 색인한 색인 목록에서 검색어에 해당하는 문서를 찾는 코드이다. 이도 동일하게 2개의 아규먼트를 전달해야한다.




2개의 간단한 색인/검색을 예제로 짜보았다. 이 코드를 짜기전에 루씬 라이브러리를 dependency 해야하는 선과정이 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- START - Lucene Dependencies -->
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-common</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-queryparser</artifactId>
      <version>5.3.0</version>
</dependency>
<!-- END - Lucene Dependencies -->
cs


posted by 여성게
:
Middleware/Zookeeper 2018. 12. 31. 23:31

Zookeeper란?,분산코디네이터



분산 처리 환경에서 필수로 언급되는 ZooKeeper란 무엇일까? 한 마디로 정의하면 "분산 처리 환경에서 사용 가능한 데이터 저장소"라고 말할 수 있겠다. 기능은 매우 단순하지만 분산 서버 환경에서는 활용 분야가 넓다. 예를 들어 분산 서버 간의 정보 공유, 서버 투입/제거 시 이벤트 처리, 서버 모니터링, 시스템 관리, 분산 락 처리, 장애 상황 판단 등 다양한 분야에서 활용할 수 있다.

'어떻게 다양한 활용이 가능할까?'라는 의문을 해결하기 위해서 기능에 대해서 한 마디로 설명하면 다음과 같이 설명할 수 있다.

ZooKeeper는 데이터를 디렉터리 구조로 저장하고, 데이터가 변경되면 클라이언트에게 어떤 노드가 변경됐는지 콜백을 통해서 알려준다. 데이터를 저장할 때 해당 세션이 유효한 동안 데이터가 저장되는 Ephemeral Node라는 것이 존재하고, 데이터를 저장하는 순서에 따라 자동으로 일련번호(sequence number)가 붙는 Sequence Node라는 것도 존재한다. 조금 과장하면 이러한 기능이 ZooKeeper 기능의 전부다. 이런 심플한 기능을 가지고 자신의 입맛에 맞게 확장해서 사용하면 된다.



기능을 하나씩 자세히 살펴보자. 우선 ZooKeeper가 데이터를 저장하는 방식인 데이타 모델에 대해서 살펴보자.

ZooKeeper는 <그림 1>과 같은 디렉터리 구조로 데이터를 저장한다. 특징을 살펴보면 Persistent를 유지하기 위해서 트랜잭션 로그와 스냅샷 파일이 디스크에 저장되어 시스템을 재시작해도 데이터가 유지된다. 각각의 디렉터리 노드를 znode라고 명명하며, 한 번 설정한 이름은 변경할 수 없고 스페이스를 포함할 수도 없다.

zookeeper1

그림 1 ZooKeeper의 데이터 모델(이미지 출처)

데이터를 저장하는 기본 단위인 노드에는 Persistent Node와 Ephemeral Node, Sequence Node, 3가지가 있다.

첫 번째로 Persistent Node는 한 번 저장되고 나면 세션이 종료되어도 삭제되지 않고 유지되는 노드다. 즉, 명시적으로 삭제하지 않는 한 해당 데이터는 삭제 및 변경되지 않는다.

두 번째로 Ephemeral Node는 특정 노드를 생성한 세션이 유효한 동안 그 노드의 데이터가 유효한 노드다. 좀 더 자세히 설명하면 ZooKeeper Server에 접속한 클라이언트가 특정 노드를 Ephermeral Node로 생성했다면 그 클라이언트와 서버 간의 세션이 끊어지면, 즉 클라이언트와 서버 간의 Ping을 제대로 처리하지 못한다면 해당 노드는 자동으로 삭제된다. 이 기능을 통해 클라이언트가 동작하는지 여부를 쉽게 판단할 수 있다.

세 번째로 Sequence Node는 노드 생성 시 sequence number가 자동으로 붙는 노드다. 이 기능을 활용해 분산 락 등을 구현할 수 있다.

다음으로 ZooKeeper 서버 구성도를 살펴보자.

zookeeper2

그림 2 ZooKeeper의 서버 구성도(이미지 출처)

ZooKeeper 서버는 일반적으로 3대 이상을 사용하며 서버 수는 주로 홀수로 구성한다. 서버 간의 데이터 불일치가 발생하면 데이터 보정이 필요한데 이때 과반수의 룰을 적용하기 때문에 서버를 홀수로 구성하는 것이 데이터 정합성 측면에서 유리하다. 그리고 ZooKeeper 서버는 Leader와 Follower로 구성되어 있다. 서버들끼리 자동으로 Leader를 선정하며 모든 데이터 저장을 주도한다.



클라이언트에서 Server(Follower)로 데이터 저장을 시도할 때 Server(Follower) -> Server(Leader) -> 나머지 Server(Follower)로 데이터를 전달하는 구조이고, 모든 서버에 동일한 데이터가 저장된 후 클라이언트에게 성공/실패 여부를 알려주는 동기 방식으로 작동한다. 즉, 모든 서버는 동일한 데이터를 각각 가지고 있고, 클라이언트가 어떤 서버에 연결되어 데이터를 가져가더라도 동일한 데이터를 가져가게 된다.

사용/운영 시 몇 가지 주의 사항이 있다. 개발에 급급한 나머지 ZooKeeper를 캐시 용도로 사용해 서비스를 오픈했다가 장애가 발생한 사례도 종종 있다고 한다. 데이터의 변경이 자주 발생하는 서비스에서 ZooKeeper를 데이터 저장소로 사용하는 것은 추천하지 않는다. ZooKeeper에서 추천하는 Read : Write 비율은 10 : 1 이상이다.

그리고 ZooKeeper 서버가 제대로 실행되지 않을 때가 있는데, 대부분 서버간의 데이터 불일치로 인한 데이터 동기화 실패가 그 원인이다. 주로 베타 테스트 후 운영 직전에 ZooKeeper 서버를 증설해서 사용하는데, 이럴 때 기존에 테스트했던 서버와 신규로 투입한 서버의 데이터 차이로 인해 이런 현상이 종종 발생한다. 이때는 데이터를 초기화한 후 서버를 실행하면 된다.

그리고 마지막으로, zoo.cfg라는 설정(configuration) 파일의 ZooKeeper 서버 목록을 꼼꼼히 확인해야 한다. 서버 목록이 정확히 맞지 않아도 서버가 실행되긴 하지만, 로그 파일을 확인하면 ZooKeeper 서버가 지속적으로 재시작할 때도 있고 데이터를 엉뚱한 곳에 저장하기도 한다.


출처:https://d2.naver.com/helloworld/294797

posted by 여성게
:
인프라/Web Server & WAS 2018. 10. 26. 13:39

이번 프로젝트에서 웹 취약성 검사에 나왔던 이슈관련 정리입니다.


1.Content-Security-Policy ->간단하게 페이지에서 사용하는 모든 자원에 대한 제한을 거는 설정입니다. 즉, XSS관련 공격을 막아줍니다.

2.X-Content-Type-Option ->MIME을 무시하지 않게 제한합니다. 즉, mime기반 공격을 막아줍니다.(반드시 정해진 mime type으로만 화면을 렌더링하게함)

3.X-XSS-Protection ->XSS 공격을 막아줍니다.


출처:http://cyberx.tistory.com/171 ->개략적인 웹 취약성을 방지해주는 헤더들의 설명이 들어간 블로그.


검사에서 나왔던 요소들이었습니다. 그래서 이러한 헤더 설정을 위하여 Apache의 httpd.conf 파일에 정의했습니다.



1
2
3
4
5
<IfModule mod_headers.c>
    Header set Content-Security-Policy "script-src 'self' ; img-src 'self'; style-src 'self' 'unsafe-inline';connect-src http:; child-src 'unsafe-inline'"
    Header set X-Content-Type-Options nosniff
    Header set X-XSS-Protection "1;mode=block"
</IfModule>
cs




해당 설정의 요소들은 구글링하면 자료를 많이 찾으실수 있습니다. 몇개만 예로 설명하면 script-src 'self' 같은 경우는 자바스크립트 코드는 해당 도메인 내의(자신의 도메인) 코드만 이용해라라고 제한하는 것이고(ex /js/script.js 같이 자신의 리소스자원에 있는), unsafe-inline 같은 경우는 보안에 좋지 않은 제한 범위이지만 html소스내에서 <script></script> 코드로 박혀있는 애들도 사용할수 있게 해라라는 뜻입니다. 해당 'self','insafe-inline'은 하나 이상 적용가능합니다. connect-src http: 같은 경우는 http:로 들어오는 모든 요청에 커넥션(ajax...등)을 받을 수 있게 합니다.


X-Content-Type-Options nosniff 같은 경우는 헤더가 응답 내용 유형을 무시하지 않도록 브라우저에 지시하므로 Internet Explorer가 선언 된 내용 유형에서 응답을 MIME 스니핑하지 못하게합니다. nosniff 옵션을 사용하면 서버에서 내용이 text / html이라고하면 브라우저는 text / html로 렌더링합니다.


X-XSS-Protection "1;mode=block" 같은 경우는 공격자가 XSS 공격을 시도할때 내장되어있는 XSS-Filter를 이용해 공격을 막을 수 있게 하는 설정입니다.




이 3개 이외에도 많은 웹 취약성을 보완해주는 설정들이 많습니다. 자신의 프로젝트 성격에 맞는 설정들을 찾으면 됩니다.



posted by 여성게
:
Web/Spring 2018. 10. 23. 23:58

사실 아예 숨기는 것이라고는 말할 수 없지만 보통 사용자들이 보이지 않는다고 생각할 수 있는 쿼리파라미터를 생략한 뷰리턴 방법입니다. 사실상 보안에 강하다라는 그런 방법은 아닙니다. 단지 보이지 않을 뿐입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.kt.chatbot.ui.controller;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
 
@Controller
public class ChatController {
 
    private final static Logger log = LoggerFactory.getLogger(ChatController.class);
    
    /*
     * yeoseong_yoon,2018/10/23
     * query string 을 숨기기 위한 처리로직 포함
     */
    @RequestMapping("/chat.do")
    public String getChatRedirect(@RequestParam(value="userId"String userId,@RequestParam(value="channel"String channel,RedirectAttributes redirectAttr) {
        
        redirectAttr.addFlashAttribute("userId",userId);
        redirectAttr.addFlashAttribute("channel",channel);
        return "redirect:/chat";
    }
    
    @RequestMapping("/chat")
    public String getChatView() {
        return "chat";
    }
}
 
 
cs


첫번째 요청 chat.do?userId=aa&channel=PC 이런 식으로 쿼리파라미터를 포함하여 요청이 넘어옵니다. 이때 넘어온 파라미터를 RedirectAttributes라는 놈으로 받아서 리턴을 다른 컨트롤러 요청으로 리다이렉트해줍니다. 그러면 redirectAttr에 저장한 값들은 기존에 model에 넣은것처럼 그대로 jsp에서 ${변수명} 형태로 받을 수 있습니다. 만약 리턴하는 뷰페이지의 확장자를 그대로 보여도 된다면 바로 페이지로 리다이렉트 하셔도됩니다.

posted by 여성게
:
Web/JPA 2018. 10. 14. 19:24

JPA는 쿼리가 너무 정적이라는 단점이 존재합니다. 이러한 단점을 극복하기 위하여 JAP+QueryDSL을 통해 type-safe하고, 자바의 메소드를 통한 동적인(유연한) 쿼리 작성이 가능해집니다.


이전 글에서 JPA사용법에 대하여 간단히 작성했지만 이번 글에서도 JPA부터 설명하겠습니다.



pom.xml 설정


JPA 및 querydsl 등 각종 dependency 설정입니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.spring</groupId>
    <artifactId>jpa</artifactId>
    <name>springjpa</name>
    <packaging>war</packaging>
    <version>1.0.0-BUILD-SNAPSHOT</version>
    <properties>
        <java-version>1.8</java-version>
        <org.springframework-version>5.0.2.RELEASE</org.springframework-version>
        <org.aspectj-version>1.6.10</org.aspectj-version>
        <org.slf4j-version>1.6.6</org.slf4j-version>
<!--querydsl version 명시-->
        <querydsl.version>4.1.4</querydsl.version>
    </properties>
    <!-- 오라클 저장소 -->
    <repositories>
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    </repositories>
    <dependencies>
<!--querydsl dependency-->
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <version>${querydsl.version}</version>
            <scope>provided</scope>
        </dependency>
 
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <version>${querydsl.version}</version>
        </dependency>
 
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
            <version>2.9.0</version>
        </dependency>
<!--JPA dependency-->
        <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-entitymanager -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.1.11.Final</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>2.0.2.RELEASE</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
        <!-- <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> 
            <version>${org.springframework-version}</version> </dependency> -->
 
        <!-- oracle -->
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ucp</artifactId>
            <version>11.2.0.3</version>
            <scope>compile</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-dbcp2 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0</version>
        </dependency>
 
        <!-- Spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${org.springframework-version}</version>
            <exclusions>
                <!-- Exclude Commons Logging in favor of SLF4j -->
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
 
        <!-- AspectJ -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${org.aspectj-version}</version>
        </dependency>
 
        <!-- Logging -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${org.slf4j-version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${org.slf4j-version}</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.15</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.mail</groupId>
                    <artifactId>mail</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>javax.jms</groupId>
                    <artifactId>jms</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jdmk</groupId>
                    <artifactId>jmxtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>com.sun.jmx</groupId>
                    <artifactId>jmxri</artifactId>
                </exclusion>
            </exclusions>
            <scope>runtime</scope>
        </dependency>
 
        <!-- @Inject -->
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
 
        <!-- Servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
 
        <!-- Test -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-eclipse-plugin</artifactId>
                <version>2.9</version>
                <configuration>
                    <additionalProjectnatures>
                        <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
                    </additionalProjectnatures>
                    <additionalBuildcommands>
                        <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
                    </additionalBuildcommands>
                    <downloadSources>true</downloadSources>
                    <downloadJavadocs>true</downloadJavadocs>
                </configuration>
            </plugin>
<!--maven plugin 추가-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.5.1</version>
                <configuration>
                    <!-- 사용할 자바버전 -->
                    <source>1.8</source>
                    <target>1.8</target>
                    <compilerArgument>-Xlint:all</compilerArgument>
                    <showWarnings>true</showWarnings>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
                <configuration>
                    <mainClass>org.test.int1.Main</mainClass>
                </configuration>
            </plugin>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
<!--Q클래스들이 생성될 위치입니다.-->
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 

cs




Entity class 작성


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package com.spring.jpa.model;
 
import java.io.Serializable;
import java.util.Date;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.PrePersist;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_USER")
public class UserEntity implements Serializable{
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="USER_ID")
    private long id;
    
    private String userName;
    private int age;
    private Date createdAt;
    @Column(name="role")
    @Enumerated(EnumType.ORDINAL) //ORDINAL -> int로 할당 STRING -> 문자열로 할당 
    private UserRole role;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name="NATION_ID")
    private NationEntity nationEntity;
    
    @PrePersist
    public void beforeCreate() {
        createdAt=new Date();
    }
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
 
    public UserRole getRole() {
        return role;
    }
 
    public void setRole(UserRole role) {
        this.role = role;
    }
 
    public NationEntity getNationEntity() {
        return nationEntity;
    }
 
    public void setNationEntity(NationEntity nationEntity) {
        this.nationEntity = nationEntity;
    }
    
    
}
 
cs



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.spring.jpa.model;
 
import java.util.Set;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
 
@Entity
@Table(name="TBL_NATION")
public class NationEntity {
    
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @Column(name="NATION_ID")
    private long id;
    private String nationName;
    private String nationCode;
    
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public String getNationName() {
        return nationName;
    }
    public void setNationName(String nationName) {
        this.nationName = nationName;
    }
    public String getNationCode() {
        return nationCode;
    }
    public void setNationCode(String nationCode) {
        this.nationCode = nationCode;
    }
    
}
 
cs


maven plugin에서 JpaAnnotationProccesor가 @Entity를 보고 관련 Q클래스를 생성해줍니다.



maven build




run 해주면 target>generate-sources/java에 해당 Q클래스들이 생성됩니다. 만약 생성이 되었지만 Q클래스들을 인식하지 못하였을 경우에는 build path에 해당 폴더를 add folder해줍니다. 



Repository 작성



1
2
3
4
5
6
7
8
9
10
package com.spring.jpa.repository;
 
import java.util.List;
 
import com.spring.jpa.model.UserEntity;
 
public interface JpaUserRepositoryCustom {
    List<UserEntity> findAllLike(String keyword);
}
 
cs


커스텀레포지터리를 작성해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.spring.jpa.repository;
 
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
import org.springframework.stereotype.Repository;
 
import com.querydsl.jpa.JPQLQuery;
import com.spring.jpa.model.QUserEntity;
import com.spring.jpa.model.UserEntity;
 
@Repository
public class JpaUserRepositoryImpl extends QuerydslRepositorySupport implements JpaUserRepositoryCustom{
 
    public JpaUserRepositoryImpl() {
        super(UserEntity.class);
        // TODO Auto-generated constructor stub
    }
 
    @Override
    @Autowired
    public void setEntityManager(EntityManager entityManager) {
        super.setEntityManager(entityManager);
    }
 
    @Override
    public List<UserEntity> findAllLike(String keyword) {
        // TODO Auto-generated method stub
        QUserEntity qUserEntity=QUserEntity.userEntity;
        JPQLQuery<UserEntity> query=from(qUserEntity);
        List<UserEntity> userEntityList=query.where(qUserEntity.userName.like(keyword)).fetch();
        return userEntityList;
    }
 
}
 
cs


커스텀레포지터리 구현체를 만들어줍니다. 주의해야할점은 생성자에서 super(domain.class)입니다. 도메인클래스 인자는 해당 구현체가 필요한 것이 아니라, QuerydslRepositorySupport이 필요한 인자입니다. 꼭 넣어주어야합니다 !!!!




그리고 커스텀인터페이스에서 선언한 메소드를 오버라이드하여, querydsl을 이용한 type-safe한 동적인 쿼리를 작성해줍니다. 여기서 querydsl은 일반 Jpa 레포지토리의 기능을 업그레이드 해주는 역할이라고 보시면 됩니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.spring.jpa.repository;
 
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
 
import com.spring.jpa.model.UserEntity;
 
@Repository("jpaUserRepository")
public interface JpaUserRepository extends JpaRepository<UserEntity, Long>,JpaUserRepositoryCustom{
 
    UserEntity findByUserName(String userName);
    
    @Query(value="SELECT * FROM TBL_USER WHERE USERNAME=?1 AND AGE=?2",nativeQuery=true)
    UserEntity findByUserNameAndAge(String userName,int age);
}
 
cs


일반 Jpa 레포지토리에 커스텀인터페이스 상속을 해줍니다. 이렇게 커스텀인터페이스를 상속해주면 커스텀인터페이스 구현체에서 구현한 querydsl 메소드를 일반 jpa레포지토리에서 사용이 가능해집니다. 즉, 일반 jpa레포지터리의 기능을 그대로 이용하면서 업그레이드된 querydsl쿼리를 같이 이용할 수 있게 되는 것입니다.



Controller 작성


대충짰습니다....controller에서 repository를 다이렉트로 주입하고... 따라하시지 마시고 테스트용도이니 만약 사용하실때는 꼭 controller->service->repository 구조로 이용해주세요 !!!!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package com.spring.jpa.controller;
 
import java.util.Arrays;
import java.util.List;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.spring.jpa.model.NationEntity;
import com.spring.jpa.model.UserEntity;
import com.spring.jpa.model.UserRole;
import com.spring.jpa.repository.JpaNationRepository;
import com.spring.jpa.repository.JpaUserRepository;
import com.spring.jpa.service.UserService;
 
@Controller
public class UserController {
    
    private final static Logger log=LoggerFactory.getLogger(UserController.class);
    
    @Autowired
    UserService userService;
    @Autowired
    JpaNationRepository jpaNationRepository;
    @Autowired
    JpaUserRepository jpaUserRepository;
    
    @GetMapping("/user")
    public String getUserView() {
        return "user";
    }
    
    @GetMapping("/usersave")
    public String saveUser(ModelMap map,UserEntity userEntity,NationEntity nationEntity) throws JsonProcessingException {
        
        nationEntity.setNationCode("KR");
        nationEntity.setNationName("대한민국");
        jpaNationRepository.save(nationEntity);
        
        userEntity.setUserName("여성게");
        userEntity.setAge(27);
        userEntity.setRole(UserRole.USER);
        userEntity.setNationEntity(nationEntity);
        userService.save(userEntity);
        
        UserEntity saveUser=userService.findByUserName("여성게");
        UserEntity user=userService.findByUserNameAndAge(saveUser.getUserName(), saveUser.getAge());
        log.info("username={},user age={}",user.getUserName(),user.getAge());
        log.info("nationCode={},nationName={}",user.getNationEntity().getNationCode(),user.getNationEntity().getNationName());
        
        XmlMapper xmlMapper=new XmlMapper();
        String xmlString=xmlMapper.writeValueAsString(user);
        
        log.info(xmlString);
        
        List<UserEntity> userList=jpaUserRepository.findAllLike("여성게");
        log.info("userEntity list={}",userList.get(0).getUserName());
        
        map.addAttribute("userEntity", saveUser);
        return "userlist";
    }
}
 
cs



확실히.. 편하긴 하지만 여기까지 오기에 복잡한 설정은 아니지만 귀찮은 설정들이 많았내요... 조금 적응되면 이 기술도 자유롭게 사용가능하겠죠? mybatis를 선호하였던 저였지만, jpa+querydsl을 잠시나마 사용해보니 확실히 기술을 익히고 사용한다면 mybatis보다 훨씬 유연한 데이터베이스 관련 기술 일것 같습니다.



혹시 여기까지 오시다가 cannot change web module version 2.5 to ....어쩌고 맞나?.. 이런 에러가 뜬다면 자기가 사용중인 web module 버전과 maven에 명시되어 있는 버전과 비교를 하시고 꼭 maven jdk 버전이랑도 같이 맞춰주시면 해당 에러는 사라집니다... 설정을 다 다시 마추고 maven update하면 에러는 싹 사라집니다. 

posted by 여성게
: