ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Repository 테이블 조작
    [공부] 프로그래밍/Spring・Spring Boot (JAVA) 2024. 3. 1. 15:29

     

    Repository 테이블 조작
    @Repository
    public interface test_table_repository extends JpaRepository<test_table, String>, JpaSpecificationExecutor<test_table> {
        Logger LOG = LoggerFactory.getLogger(test_table_repository.class);
    
        /**
         * 검색
         *
         * @param testId
         */
        List<test_table> findByTestId(String testId);
    
        /**
         * 「AND」완전일치검색
         *
         * @param test_table
         * @return
         */
        default List<test_table> get_find_and(test_table test_table) {
            
            return findAll(Example.of(test_table));
        }
    
    
        /**
         * 추가
         * 
         * @param List<test_table>
         */
        @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Exception"})
        default void add_List(List<test_table> test_table) {
            test_table.stream().forEach(i -> {
                save(i);
            });
        }
    
        /**
         * 갱신
         * 
         * @param List<test_table>
         */
        @Modifying
        @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Exception"})
        default void update_List(List<test_table> test_table) {
            test_table.stream().forEach(i -> {
                Optional<test_table> tt = findById(i.getTestId());
                if (tt.stream().count() > 0) {
                    save(i);
                } else {
                    // 갱신대상 없음
                    throw new NullPointerException(
                            MessageFormat.format("ID [{0}]", i.getTestId())
                    );
                }
            });
        }
    
    
        /**
         * 삭제
         * 
         * @param List<test_table>
         */
        @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Exception"})
        @Modifying
        default void deleteById(List<test_table> test_table) {
            test_table.stream().forEach(i -> {
                i.setNewrec(false);
                this.delete(i);
            });
        }
    }

    ◇ JpaSpecificationExecutor<test_table>

    JpaSpecificationExecutor<test_table>는 Spring Data JPA에서 제공하는 인터페이스이다. 이 인터페이스는 JPA Criteria API를 사용하여 동적인 쿼리를 생성하고 실행할 수 있게 해준다. 일반적으로 개발자들은 여러 가지 검색 조건을 고려하여 데이터베이스에서 엔티티를 조회해야 할 때가 있다.
    예를 들어, 사용자가 검색 폼에서 여러 가지 필터를 선택하거나 입력할 수 있다. 이러한 경우에는 정적인 JPQL 쿼리를 사용하는 것보다는 동적인 쿼리를 생성하는 것이 효과적일 수 있다. JpaSpecificationExecutor<test_table>를 구현한 리포지토리 인터페이스를 사용하면, 개발자는 다양한 검색 조건을 조합하여 동적인 쿼리를 생성할 수 있다.
    이 인터페이스의 주요 메서드 중 하나인 findAll(Specification<T> spec)는 지정된 조건에 따라 엔티티를 조회하는 데 사용된다. 개발자는 Specification 인터페이스를 구현하여 필요한 검색 조건을 정의하고, 이를 조합하여 원하는 동적 쿼리를 만들 수 있다.

    결론적으로, JpaSpecificationExecutor<test_table>는 Spring Data JPA를 사용하여 동적인 검색을 수행할 수 있는 인터페이스이며, test_table 엔티티에 대한 동적인 쿼리 작성을 가능하게 한다.

     
    ◇ @Transactional(propagation = Propagation.REQUIRED, rollbackForClassName = {"Exception"})

    @Transactional은 메서드나 클래스에 트랜잭션을 적용하고, 메서드 실행 도중 예외가 발생하면 롤백을 수행하는데 사용된다.

    ・ propagation = Propagation.REQUIRED: 현재 트랜잭션이 있으면 그 트랜잭션을 사용하고, 없으면 새로운 트랜잭션을 시작한다. 이는 메서드가 다른 메서드를 호출할 때 해당 메서드가 이미 트랜잭션 내에서 실행 중인지 여부를 나타낸다.

    ・ rollbackForClassName = {"Exception"}: 지정된 예외 클래스가 발생하면 롤백을 수행한다. 여기서는 모든 예외 클래스에 대해 롤백을 수행하도록 "Exception"을 지정하였다. 즉, 위의 애노테이션은 해당 메서드에서 실행되는 작업을 트랜잭션으로 묶고, 메서드 실행 중에 예외가 발생하면 트랜잭션을 롤백하여 데이터 일관성을 유지하는 역할을 한다.

     
    ◇ return findAll(Example.of(test_table));

    Spring Data JPA에서 제공되는 메서드 중 하나이다. 이 메서드는 주어진 예제 객체를 사용하여 엔티티를 조회하는 데 사용된다. 여기서 Example.of(test_table)은 특정 엔티티 객체(test_table)를 기반으로 검색 조건을 정의하는 데 사용된다. 이 예제 객체에는 검색할 엔티티의 필드에 대한 값이 포함된다. 따라서 findAll(Example.of(test_table))은 주어진 예제 객체를 기반으로 데이터베이스에서 해당 조건을 만족하는 모든 엔티티를 검색하고 반환하는 메서드이다.

     
    ◇ i.setNewrec(false);

    newrec 속성은 일반적으로 "new record"의 줄임말로, 객체가 새로운 레코드인지를 나타내는 플래그이다. 이 속성이 true로 설정되면 객체가 새로운 레코드를 나타내고, 데이터베이스에 삽입될 때 새로운 레코드로 취급된다.
    반대로 false로 설정되면 해당 객체는 이미 존재하는 레코드를 나타내며, 데이터베이스에서 업데이트되거나 삭제될 때 기존 레코드를 대상으로 작업이 수행된다. 즉, i.setNewrec(false);는 객체 i가 새로운 레코드가 아니라는 것을 명시적으로 설정하는 것이다.

    부분일치 검색 처리
    default List<test_1> findIn(List<test_1> _test_1) {
    
        List<String> listId = new ArrayList<>();
        List<String> listInsertdatetime = new ArrayList<>();
        List<String> listLastupdatetime = new ArrayList<>();
    
        _test_1.stream().forEach(test_1 -> {
            listId.add(test_1.getId());
            listInsertdatetime.add(test_1.getInsertdatetime());
            listLastupdatetime.add(test_1.getLastupdatetime());
    
        });
    
        listId.removeAll(Collections.singleton(null));
        listInsertdatetime.removeAll(Collections.singleton(null));
        listLastupdatetime.removeAll(Collections.singleton(null));
    
    
        Specification<test_1> spec = Specification
                .where(Search_condition_id(listId))
                .and(Search_condition_insertdatetime(listInsertdatetime))
                .and(Search_condition_lastupdatetime(listLastupdatetime));
    
    
        return findAll(spec);
    
    }
    
    private Specification<test_1> Search_condition_id(List<String> id) {
        return id.size() == 0 ? null : (Specification<test_1>) (root, query, cb) -> root.get(test_1.id).in(id);
    }
    
    private Specification<test_1> Search_condition_insertdatetime(List<String> insertdatetime) {
        return insertdatetime.size() == 0 ? null : (Specification<test_1>) (root, query, cb) -> root.get(test_1.insertdatetime).in(insertdatetime);
    }
    
    private Specification<test_1> Search_condition_lastupdatetime(List<String> lastupdatetime) {
        return lastupdatetime.size() == 0 ? null : (Specification<test_1>) (root, query, cb) -> root.get(test_1.lastupdatetime).in(lastupdatetime);
    }

     

    ▷ findIn 메서드:
    findIn 메서드는 List<test_1>을 인자로 받아들입니다. 이 메서드는 각 test_1 객체의 id, insertdatetime, lastupdatetime 값을 따로따로 가져와서 각각의 리스트에 저장합니다. 그런 다음, 각 리스트에서 null 값을 제거합니다. 마지막으로, 각 Search_condition_* 메서드를 사용하여 해당 값들에 대한 Specification을 만들고 이를 합쳐서 새로운 Specification을 만듭니다. 이렇게 생성된 Specification을 사용하여 데이터를 조회한 후 결과를 반환합니다.

    ▷ Search_condition_id, Search_condition_insertdatetime, Search_condition_lastupdatetime 메서드:
    각각의 메서드는 id, insertdatetime, lastupdatetime 값을 받아들입니다. 만약 값이 없다면(null이거나 비어있다면) 해당 필터를 건너뛰기 위해 null을 반환합니다. 값이 있다면 Specification을 생성하여 해당 필드가 주어진 값들을 포함하는지를 검사하는 로직을 구현합니다.

    Collections.singleton(null)
    단일 요소를 포함하는 변경 불가능한 Set을 만드는 메서드입니다. 여기서 요소는 null입니다. 이 메서드를 호출하면 Set에는 단 하나의 요소, 즉 null 값만 포함되게 됩니다. 

    이렇게 생성된 Set은 단 하나의 요소만 포함하므로, 다른 요소를 추가할 수 없습니다. 만약 Set에 이미 null이 포함되어 있다면 추가해도 변화가 없습니다. 그리고 null 값만이 포함되어 있으므로, 다른 요소와 비교할 때 사용될 수 있습니다. 

    위 코드에서 사용된 Collections.singleton(null)은 리스트에서 null 값을 모두 제거하기 위해 사용되었습니다. 리스트에서 null 값을 제거하려면 removeAll 메서드를 사용해야 하는데, 이때 제거할 값으로 null을 지정하기 위해 Collections.singleton(null)이 사용되었습니다. 이렇게 하면 리스트에서 모든 null 값이 제거되고, 남은 값들만 남게 됩니다.

    -> 여기서 Set이란
    자바 컬렉션 프레임워크의 인터페이스 중 하나입니다. Set은 중복을 허용하지 않고, 순서가 없는 요소들의 집합을 나타냅니다. 즉, 동일한 요소를 중복해서 포함할 수 없으며, 요소들 간에 순서가 정해져 있지 않습니다.

    Set은 중복을 허용하지 않는 특성을 갖기 때문에, 유일한 요소들의 집합을 표현하기에 적합합니다. 이를 통해 데이터 중복을 방지하거나 데이터의 고유한 값만을 유지할 수 있습니다.

    자바에서는 여러 가지 Set 구현체를 제공합니다. 가장 일반적인 Set 구현체로는 HashSet, TreeSet, LinkedHashSet이 있습니다.
    ・ HashSet: 해시 알고리즘을 사용하여 요소들을 저장하며, 순서를 유지하지 않습니다. HashSet은 중복된 요소를 허용하지 않으며, 요소를 빠르게 검색할 수 있습니다.
    ・ TreeSet: 이진 검색 트리를 사용하여 요소들을 저장하며, 요소들을 정렬된 순서로 유지합니다. 중복된 요소를 허용하지 않으며, 요소를 정렬된 순서로 순회할 수 있습니다.
    ・ LinkedHashSet: 해시 테이블과 연결 리스트를 사용하여 요소들을 저장하며, 요소들의 삽입 순서를 유지합니다. 따라서 순서가 중요한 경우에 사용될 수 있습니다.

    ▷ Specification
    Specification은 JPA Criteria API를 기반으로 작성되어 있으며, 동적인 쿼리를 생성하는 데 사용됩니다. 여기서 Specification<test_1>은 test_1 엔티티를 조회하기 위한 Specification을 나타냅니다.
    따라서 위 코드는 listId, listInsertdatetime, listLastupdatetime에 따라 생성된 세 개의 Specification을 모두 만들고, 이를 AND 조건으로 묶어서 하나의 Specification으로 만든 후에 spec 변수에 할당합니다.

    ▷ return id.size() == 0 ? null : (Specification<test_1>) (root, query, cb) -> root.get(test_1.id).in(id);
    (Specification<test_1>): 반환되는 것이 Specification 형식이므로, 이를 명시적으로 캐스팅합니다. 즉, 반환되는 결과를 Specification<test_1> 타입으로 변환합니다.

    (root, query, cb) -> root.get(test_1.id).in(id): 이 부분은 Specification의 구현체를 생성하는 람다 표현식입니다. 람다 표현식은 세 개의 인자(root, query, cb)를 받으며, 각각은 JPA Criteria API의 요소들을 나타냅니다.
      ・ root: 엔티티의 루트를 나타냅니다. 여기서는 test_1 엔티티를 가리킵니다.
      ・ query: JPA 쿼리를 나타냅니다. 여기서는 사용되지 않습니다.
      ・ cb: CriteriaBuilder 인스턴스를 나타냅니다. CriteriaBuilder는 JPA Criteria API에서 쿼리를 생성하기 위해 사용됩니다.

    람다 표현식의 본문은 root.get(test_1.id).in(id)입니다. 여기서:
      ・ root.get(test_1.id): test_1 엔티티의 id 속성에 대한 Path를 얻습니다.
        Path는 엔티티의 특정 속성을 나타내는 데 사용됩니다. 
      ・ .in(id): id 리스트에 포함된 값들 중에서 해당 엔티티의 id 속성이 포함되어 있는지를 확인합니다. in 메서드는 주어진 값들 중 하나와 일치하는지를 확인하는 연산자입니다.

    따라서 이 코드는 id 리스트에 값이 포함되어 있으면 해당 값을 포함하는 Specification을 생성하고, 그렇지 않으면 null을 반환합니다.
    ・find 함수: 
    일반적으로 Spring Data JPA 리포지토리 인터페이스에서 정의됩니다. 
    주어진 조건에 맞는 결과를 가져오는데 사용됩니다. 
    정적인 쿼리를 사용하며, 쿼리가 미리 정의되어 있습니다. 
    따라서 동적인 조건으로 검색할 수 없습니다. 
    특정 엔티티의 기본 키(주로 ID)를 기반으로 조회됩니다. 
    주로 CRUD 작업 중 하나를 수행하기 위해 사용됩니다. 
    예를 들어, findById, findByUsername, findByCreateDateBetween과 같은 메서드가 포함됩니다. 
    
    ・Spring Data JPA의 Specification을 사용한 검색: 
    동적인 검색을 위해 사용됩니다. 
    즉, 런타임에 다양한 조건을 적용하여 쿼리를 생성합니다. 
    Specification은 JPA Criteria API를 사용하여 작성됩니다. 
    여러 조건을 조합하여 동적인 쿼리를 생성할 수 있습니다. 
    예를 들어, 여러 엔티티 속성에 대한 조건을 조합하거나, 
    AND나 OR 같은 논리 연산자를 사용할 수 있습니다. 
    주로 복잡한 검색 조건이 필요한 경우 사용됩니다. 
    예를 들어, 여러 속성의 값이 동적으로 주어지는 경우나, 
    다양한 검색 옵션을 지원하는 화면이 있는 경우에 유용합니다. 
    일반적으로 Spring Data JPA의 findAll(Specification) 메서드를 사용하여 실행됩니다. 
    이 메서드는 주어진 Specification에 따라 엔티티를 조회합니다. 
    
    따라서 find 함수는 미리 정의된 정적인 쿼리를 사용하여 검색하는 반면에, 
    Specification을 사용한 검색은 동적인 조건에 따라 쿼리를 생성하여 유연하게 데이터를 조회하는 데 사용됩니다.

    '[공부] 프로그래밍 > Spring・Spring Boot (JAVA)' 카테고리의 다른 글

    암호화, 복호화 처리  (0) 2024.03.14
    enum  (0) 2024.03.05
    Bean Validation 처리  (0) 2024.03.01
    Enum에 해당 값 존재 여부 체크 처리  (1) 2023.10.10
    JWT 토큰 인증 처리  (0) 2023.09.11
Designed by Tistory.