TIL 50 [ JPA 심화 - ( 테이블 객체 다루는법, JpaRepository 쿼리,페이징처리, JPQL ,QueryDSL, Auditing, 필요한부분 갱신 ) , 트랜잭션 복습 ]

2025. 5. 2. 22:13·[내일배움캠프-Sparta]/Spring 6기
반응형

1️⃣ 테이블 객체 다루는법

✅ Cascade ( 영속성 전이 )

🔥 쉽게 생각하면 부모가 삭제되면, 자식도 삭제되는 개념이다. ( 부모가 수정되면, 자식도 수정 )

사용 위치

  • 연관관계의 주인 반대편 - 부모 엔티티(다대일에서 일)
  • 즉, @OneToMany 가 있는 쪽 또는 @OneToOne 도 가능
  • 예) 게시글과 첨부파일이라면 일에 해당하는 게시글에 설정한다.

사용 조건

  • 양쪽 엔티티의 라이프사이클이 동일하거나 비슷해야한다.
    • 예를들어, 게시글이 삭제되면 첨부파일도 같이 삭제 되어야 한다.
  • 대상 엔티티로의 영속성 전이는 현재 엔티티에서만 전이 되어야 한다. (다른곳에서 또 걸면 안됨)
    • 예를들어, 첨부파일을 게시글이 아닌 다른곳에서 영속성 전이를 하면 안된다.

옵션 종류

  • ALL : 전체 상태 전이 ⭐
  • PERSIST : 저장 상태 전이
  • REMOVE : 삭제 상태 전이 ⭐
  • MERGE : 업데이트 상태 전이
  • REFERESH : 갱신 상태 전이
  • DETACH : 비영속성 상태 전이

✅ orphanRemoval ( 고아 객체 제거 )

사용 위치

  • @OneToMany 또는 @OneToOne 에서 사용 - 부모 엔티티

사용법

  • Cascade.REMOVE 와 비슷한 용도로 삭제를 전파하는데 쓰인다.
  • 부모 객체에서 리스트 요소삭제를 했을경우 해당 자식 객체는 매핑정보가 없어지므로 대신 삭제해준다.
    • 요건 DB 에서는 절대 알 수 없는 행동이다. (부모가 자식의 손을 놓고 버리고 간 고아 객체)
Parent parent1 = em.find(Parent.class, parent.getId());
parent1.getChildList().remove(0); // delete 쿼리나간다.
더보기

💁‍♂️ 그렇다면 Cascade.REMOVE 와 orphanRemoval 차이점은 무엇인가?orphanRemoval=true는 위 케이스도 포함하며,일에 해당하는 부모 엔티티의 리스트에서 요소를 삭제하기만 해도 해당 다에 해당하는 자식 엔티티가 delete되는 기능까지 포함하고 있다고 이해하시면 됩니다.

 

Cascade.REMOVE의 경우 일에 해당하는 부모 엔티티를 em.remove를 통해 직접 삭제할 때,그 아래에 있는 다에 해당하는 자식 엔티티들이 삭제되는 것입니다.

 

즉, orphanRemoval=true 는 리스트 요소로써의 영속성 전이도 해준다는 뜻

 

* 옵션 true / false

 ✅ 영속성 전이 최강 조합 : orphanRemoval=true + Cascade.ALL

위 2개를 함께 설정하면 자식 엔티티의 라이프 사이클이 부모 엔티티와 동일해지며, 직접 자식 엔티티의 생명주기를 관리할 수 있게 되므로 자식 엔티티의 Repository 조차 없어도 된다. (따라서, 매핑 테이블에서 많이 쓰임)

 

✅ Fetch ( 조회 시점 )

사용 위치

  • Entity 에 FetchType 으로 설정할 수 있다.
    • @ElementCollection, @ManyToMany, @OneToMany, @ManyToOne, @OneToOne
  • Query 수행시 fetch Join 을 통해서 LAZY 인 경우도 즉시 불러올 수 있다.

사용법

  • 기본 LAZY를 설정한 뒤에 필요할때만 fetch Join 을 수행한다.
  • 항상 같이 쓰이는 연관관계 일 경우만 EAGER 를 설정한다.

옵션(FetchType)

  • EAGER : 즉시 로딩 (부모 조회 시 자식도 같이 조회)
  • LAZY : 지연 로딩 (자식은 필요할때 따로 조회)

2️⃣ 테이블 객체로 자동 쿼리 생성하기 ( JpaRepository 쿼리 )

✅ JpaRepository 쿼리 기능

SprintData Common 의 CRUDRepository + PagingAndSortingRepository 이 쿼리기능을 제공

더보기

✅ 쿼리 사용법

프로그래밍되어 제공되는 쿼리명 규칙

  • 리턴타입 {접두어}{도입부}By{프로퍼티 표현식}(조건식) [ (And|Or)  {프로퍼티 표현식}(조건식) ] (OrderBy {  (매개변수...)
// 기본
List<User> findByNameAndPassword(String name, String password);

// distinct (중복제거)
List<User> findDistinctUserByNameOrPassword(String name, String password);
List<User> findUserDistinctByNameOrPassword(String name, String password);

// ignoring case (대소문자 무시)
List<User> findByNameIgnoreCase(String name);
List<User> findByNameAndPasswordAllIgnoreCase(String name, String password);

// 정렬
List<Person> findByNameOrderByNameAsc(String name);
List<Person> findByNameOrderByNameDesc(String name);

// 페이징
Page<User> findByName(String name, Pageable pageable);  // Page 는 카운트쿼리 수행됨
Slice<User> findByName(String name, Pageable pageable); // Slice 는 카운트쿼리 수행안됨
List<User> findByName(String name, Sort sort);
List<User> findByName(String name, Pageable pageable);

// 스트림 (stream 다쓴후 자원 해제 해줘야하므로 try with resource 사용추천)
Stream<User> readAllByNameNotNull();

더보기

🔍 Pageable vs Sorting

Pageable

  • Pageable 인터페이스를 구현한 PageRequest 객체를 만들거나 얻습니다.
  • PageRequest 객체를 repository 메소드에 우리가 의도한데로 인자로 전달합니다.
  • PageRequest 객체는 요청된 페이지 숫자와 페이지 사이즈를 넘김으로서 만듭니다. (페이지 숫자는 0부터 시작합니다)
// 첫 페이지 (페이지 사이즈 = 2)
Pageable firstPageWithTwoElements = PageRequest.of(0, 2);
// 두번째 페이지 (페이지 사이즈 = 5)
Pageable secondPageWithFiveElements = PageRequest.of(1, 5);

// 페이지 사용
List<Product> allTenDollarProducts =
productRepository.findAllByPrice(10, secondPageWithFiveElements);

Sorting

  • 유사하게, 우리의 쿼리 결과를 정렬하기 위해선 Sort 객체를 메소드에 전달하면 됩니다.
  • 만약, 정렬과 페이지네이션을 둘다 하고 싶다면 어떻게해야할까요?
    • 정렬에 대한 디테일 정보를 PageRequest 객체에 전달하면 됩니다.
Pageable sortedByName = PageRequest.of(0, 3, Sort.by("name"));

Pageable sortedByPriceDesc = PageRequest.of(0, 3, Sort.by("price").descending());

Pageable sortedByPriceDescNameAsc = PageRequest.of(0, 5, Sort.by("price").descending().and(Sort.by("name")));

페이징 없이 정렬만 하려는 경우 위에 언급한 findAll(Sort sort) 메서드와 같이 Sort 객체만 파라미터로하는 메서드를 작성하면 된다.

Page<Product> allProductsSortedByName = productRepository.findAll(Sort.by("name").accending());

✅ 효율적인 사용법

Optional 제거하기

Spring Data JPA의 findByXX 메서드는 기본적으로 Optional을 반환한다. 이로 인해 비즈니스 로직에서 Optional 처리를 위한 추가적인 작업이 필요하게 되는데, 이럴 때 default 메서드를 활용하면 이 문제를 우아하게 해결

더보기
public interface UserRepository extends JpaRepository<User, Long> {
// Default 메소드를 사용하여 findById의 Optional을 내부적으로 처리
default User findUserById(Long id) {
        return findById(id).orElseThrow(() -> new DataNotFoundException("User not found with id: " + id));
    }
}

메서드명 간소화하기

Spring Data JPA를 사용하다 보면 복잡한 쿼리 때문에 메서드명이 길어져 가독성을 해치는 경우가 있다. 이럴 때도 default 메서드를 활용하면 긴 메서드명을 간결하고 명확하게 표현

더보기
public interface ProductRepository extends JpaRepository<Product, Long> {
// 기존의 긴 쿼리 메소드
List<Product> findAllByCategoryAndPriceGreaterThanEqualAndPriceLessThanEqualOrderByPriceAsc(String category, BigDecimal minPrice, BigDecimal maxPrice);

// Default 메소드를 사용하여 간결한 메소드명 제공
default List<Product> findProductsByCategoryAndPriceRange(String category, BigDecimal minPrice, BigDecimal maxPrice) {
        return findAllByCategoryAndPriceGreaterThanEqualAndPriceLessThanEqualOrderByPriceAsc(category, minPrice, maxPrice);
    }
}

비즈니스 로직 통합

여러 기본 제공 메서드를 하나의 고차 작업으로 결합할 수도 있다. 다만 Spring Data JPA의 Repository는 Data Access Layer의 일부로, 데이터베이스와의 상호작용만을 담당하는 것이 일반적이기 때문에 이 부분은 서비스 레이어에서 처리하는 것이 일반적

더보기
public interface UserRepository extends JpaRepository<User, Long> {

// 사용자 ID로 사용자를 찾고, 존재할 경우 연락처 정보를 업데이트하는 메소드
default void updateUserContact(Long userId, String newContact) {
        findById(userId).ifPresent(user -> {
            user.setContact(newContact);
            save(user);
        });
    }
}

3️⃣ 테이블 객체로 페이지 조회하기

페이지네이션을 사용하여 대량의 데이터를 효율적으로 조회하는 방법을 배웁니다. Spring Data JPA에서 제공하는 Pageable 인터페이스를 사용하는 실습을 통해 데이터 처리 성능을 최적화하는 방법을 학습

✅ 페이징 레포지토리

JpaRepository 의존성

  • ListPagingAndSortingRepository
    • PagingAndSortingRepository : 여기에서 페이징 & 소팅 기능을 제공
더보기

✅ 페이징 처리 프로세스

  1. PageRequest 를 사용하여 Pageable에 페이징 정보를 담아 객체화 한다.
  2. Pageable을 JpaRepository가 상속된 인터페이스의 메서드에 T(Entity)와 함꼐 파라미터로 전달한다.
  3. 2번의 메서드의 return 으로 Page< T >가 응답 된다.
  4. 응답된 Page< T >에 담겨진 Page 정보를 바탕으로 로직을 처리

✅ 페이징 요청/응답 클래스

Pageable

  • 요청 : org.springframework.data.domain.Pageable
    • 페이징을 제공하는 중요한 인터페이스
더보기
Pageable 만드는법
PageRequest.of(int page, int size) : 0부터 시작하는 페이지 번호와 개수. 정렬이 지정되지 않음
PageRequest.of(int page, int size, Sort sort) : 페이지 번호와 개수, 정렬 관련 정보
PageRequest.of(int page int size, Sort sort, Direction direction, String ... props) : 0부터 시작하는 페이지 번호와 개수, 정렬의 방향과 정렬 기준 필드들​
pageable.getTotalPages() : 총 페이지 수
pageable.getTotalElements() : 전체 개수
pageable.getNumber() : 현재 페이지 번호
pageable.getSize() : 페이지 당 데이터 개수
pageable.hasnext() : 다음 페이지 존재 여부
pageable.isFirst() : 시작페이지 여부
pageable.getContent(), PageRequest.get() : 실제 컨텐츠를 가지고 오는 메서드. getContext는 List<Entity> 반환, get()은 Stream<Entity> 반환​
  • 응답 : org.springframework.data.domain.Page
    • 페이징의 findAll() 의 기본적인 반환 메서드로 여러 반환 타입 중 하나
더보기

Paging 응답 펼쳐보기

{
    "content": [
        {"id": 1, "username": "User 0", "address": "Korea", "age": 0},
        ...
        {"id": 5, "username": "User 4", "address": "Korea", "age": 4}
    ],
    "pageable": {
        "sort": {
            "sorted": false, // 정렬 상태
            "unsorted": true,
            "empty": true
        },
        "pageSize": 5, // 페이지 크기
        "pageNumber": 0, // 페이지 번호 (0번 부터 시작)
        "offset": 0, // 해당 페이지의 첫번째 요소의 전체 순번 (다음 페이지에서는 5)
        "paged": true,
        "unpaged": false
    },
    "totalPages": 20, // 페이지로 제공되는 총 페이지 수
    "totalElements": 100, // 모든 페이지에 존재하는 총 원소 수
    "last": false,  // 마지막 페이지 여부
    "number": 0,
    "sort": {
        "sorted": false,    // 정렬 사용 여부
        "unsorted": true,
        "empty": true
    },
    "size": 5,       // Contents 사이즈
    "numberOfElements": 5,  // Contents 의 원소 수
    "first": true,   // 첫페이지 여부
    "empty": false   // 공백 여부
}

✅ 페이지 반환 타입

Page<T> 타입

  • 게시판 형태의 페이징에서 사용된다.
  • 전체 요소 갯수도 함께 조회한다. (totalElements)
  • 응답은 위와 동일

Slice<T> 타입

  • 더보기 형태의 페이징에서 사용된다.
  • 전체 요소 갯수 대신 offset 필드로 조회할 수 있다.
    • 따라서 count 쿼리가 발생되지 않고 limit+1 조회를 한다. (offset 은 성능이 안좋아서 현업에서 안씁니다)

List<T> 타입

  • 전체 목록보기 형태의 페이징에서 사용된다.
  • 기본 타입으로 count 조회가 발생하지 않는다.

✅ 정렬

컬럼 값으로 정렬하기

  • Sort 클래스를 사용
더보기
Sort sort1 = Sort.by("name").descending();     // 내림차순
Sort sort2 = Sort.by("password").ascending();  // 오름차순
Sort sortAll = sort1.and(sort2);      // 2개이상 다중정렬도 가능하다
Pageable pageable = PageRequest.of(0, 10, sortAll);  // pageable 생성시 추가

컬럼이 아닌값으로 정렬하기

  • @Query 사용시 Alias(쿼리에서 as 로 지정한 문구) 를 기준으로 정렬할 수 있다.
더보기
// 아래와 같이 AS user_password 로 Alias(AS) 를 걸어주면
@Query("SELECT u.user_name, u.password AS user_password FROM user u WHERE u.username = ?1")
List<User> findByUsername(String username, Sort sort);

// 이렇게 해당 user_password 를 기준으로 정렬할 수 있다.
List<User> users = findByUsername("user", Sort.by("user_password"));

SQL 함수를 사용해서 정렬하기

  • JpaSort 를 사용해서 쿼리 함수를 기준으로 정렬할 수 있다.
더보기
// 아래와 같이 일반적인 쿼리에서
@Query("SELECT u FROM user u WHERE u.username = ?1") // 이건 없어도됨
List<User> findByUsername(String username, Sort sort);

// 이렇게 쿼리함수 LENGTH() 조건을 걸어서 password 문자길이 기준으로 정렬할 수 있다.
List<User> users = findByUsername("user", JpaSort.unsafe("LENGTH(password)"));

4️⃣ 테이블 객체로 수동 쿼리 생성

JPQL을 사용하여 복잡한 쿼리를 수동으로 작성하고 실행하는 방법을 배웁니다.

JPQL 쿼리를 통해 표준 SQL에서 지원하지 않는 기능을 구현하는 방법을 배웁니다.

✅ JPQL

📌 JPQL (Java Persistence Query Language)

Table 이 아닌 Entity(객체) 기준으로 작성하는 쿼리를 JPQL 이라고 하며

이를 사용할 수 있도록 EntityManger 또는 @Query 구현체를 통해 JPQL 쿼리를 사용할 수 있다.

  • SQL : Table 명으로 쿼리짤때 쓰이는 언어 (쓰이는곳. JDBC, SQL Mapper)
  • JPQL : Entity 명으로 쿼리짤때 쓰이는 언어 (쓰이는곳. JPQL, QueryDSL )

EntityMananger.createQuery()

  • 쿼리 문자열과 Entity 를 직접 넣어서 쿼리를 작성한다.
  • setParameter 와 같이 key, value 문자열을 통해서 쿼리 파라미터를 매핑
더보기
@Test
public void testEmCreateQuery() {
    String qlString = "select u from User u " +
                        "where u.username = :username";

    Member findUser = em.createQuery(qlString, User.class)
            .setParameter("username", "teasun")
            .getSingleResult();

    assertThat(findUser.getUsername()).isEqualTo("teasun");
}

💁‍♂️ 여기서 잠깐!! 코드에 문자열이 들어가는게 왜 안좋은건가요?

(면접 질문 가능✔️)

  1. 문자열은 오타가 발생할 여지가 많다.
  2. 개발할때 같은 공통적인 문자열이 있을때 한군데에서 수정이 일어나면 모두 수정해야한다.
  3. 잘못된 코드가 있더라도 문자열 자체를 컴파일러가 검사 하지는 않기 때문에 컴파일 시점에 잡지못한다.
  4. 이로인해 버그가 있더라도 메소드를 실행하는 시점인 런타임시점에 버그가 발생한다.
  5. 런타임 시점에 발생한 버그는 서비스 정합성에 영향을 주며 원인을 찾기도 어렵다.

해결방법,

  • 문자열을 포함하여 구현된 기능들은 객체화 또는 함수화 해서 컴파일시 체크되도록 한다.
  • 문자열로 선언된 변수들은 상수로 선언하여 공통적으로 관리한다. (상수 클래스 선언 추천 👍) 

@Query (repository interface)

  • @Query 의 인자값으로 간단하게 쿼리를 작성할 수 있습니다.
    • 쿼리를 작성할때는 테이블명이 아니라 Entity 명으로 조회

변수 바인딩은 2가지 방법

?변수순번 사용

더보기
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("SELECT u, u.password AS customField FROM User u WHERE u.username = ?1")
  List<User> findByUsernameWithCustomField(String username, Sort sort);


  @Query("SELECT u FROM User u WHERE u.username = ?1")
  List<User> findByUsername(String username, Sort sort);
}

:변수명 사용

더보기
public interface UserRepository extends JpaRepository<User, Long> {

  @Query("SELECT u, u.password AS customField FROM User u WHERE u.username = :username")
  List<User> findByUsernameWithCustomField(String username, Sort sort);


  @Query("SELECT u FROM User u WHERE u.username = :username")
  List<User> findByUsername(String username, Sort sort);
}

 

5️⃣ 더 멋지게 쿼리 생성 ( QueryDSL )

✅ QueryDSL

QueryDSL 소개

  • Entity 의 매핑정보를 활용하여 쿼리에 적합하도록 **쿼리 전용 클래스(Q클래스)**로 재구성해주는 기술 입니다.
  • 여기에 JPAQueryFactory 을 통한 Q클래스를 활용할 수 있는 기능들을 제공합니다.
  • SQL을 자바 코드처럼 작성하게 해주는 도구

JPAQueryFactory ?

  • 재구성한 Q클래스를 통해 문자열이 아닌 객체 또는 함수로 쿼리를 작성하고 실행하게 해주는 기술
@PersistenceContext
EntityManager em;
 
public List<User> selectUserByUsernameAndPassword(String username, String password){
    JPAQueryFactory jqf = new JPAQueryFactory(em);
    QUser user = QUser.user;

    List<Person> userList = jpf
                    .selectFrom(user)
                    .where(person.username.eq(username)
                        .and(person.password.eq(password))
                    .fetch();

    return userList;
}

QueryDSL 적용 방법

더보기
  • User : 유저 정보로 채널과 관계만 양방향이고, 다른 도메인과는 단방향 관계를 가집니다.
  • Channel : 대화 채널은 유저와 다대다 관계를 가진다.
  • Thread : 채널내 대화 쓰레드로 Post 와 같이 댓글, 이모지, 멘션과 관계를 가진다.
  • Comment : 쓰레드내 댓글로 쓰레드와 다대일 관계를 가지며 이모지, 멘션과도 관계를 가진다.
  • Emotion : 쓰레드, 댓글내 이모지로 쓰레드, 댓글과 다대다 관계를 가집니다.
  • Mention : 쓰레드, 댓글내 멘션으로 쓰레드, 댓글과 다대다 관계를 가집니다.

✔ JPAQueryFactory 사용을 위해 추가해야할 코드

더보기
// configuration 패키지안에 추가

@Configuration
public class JPAConfiguration {

  @PersistenceContext
  private EntityManager entityManager;

  @Bean
  public JPAQueryFactory jpaQueryFactory() {
    return new JPAQueryFactory(entityManager);
  }
}

6️⃣ 테이블 객체 방명록 설정 ( Auditing )

✅ 소개

Auditing 소개

  • Auditing 을 사용하면 엔티티를 누가 언제 생성/마지막 수정 했는지 자동으로 기록되게 할 수 있다.
  • 방명록처럼 ~~왔다감

날짜와 작성자를 기록

@CreatedDate
private Date created;

@LastModifiedDate
private Date updated;

@CreatedBy
@ManyToOne
private Account createdBy;

@LastModifiedBy
@ManyToOne
private Account updatedBy;

✅ Auditing 적용법

  1. 메인 애플리케이션 위에 @EnableJpaAuditing 추가
  2. 엔티티 클래스 위에 @EntityListeners(AuditingEntityListener.class) 추가
  3. AuditorAware 구현체 만들기
    1. createdAt, modifiedAt 은 구현체 없이 동작하지만 createdBy, modifiedBy 는 구현체가 필요하다.
    2. SpringSecurity 의 SecurityContextHolder 에서 인증정보안에 담긴 UserDetailsImpl 을 사용하여 user 객체를 가져와서 넣어준다.
  4. @EnableJpaAuditing에 AuditorAware 빈 이름 설정하기
// 3번
@Service
public class UserAuditorAware implements AuditorAware<User> {
    @Override
    public Optional<User> getCurrentAuditor() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
			
        if (authentication == null || !authentication.isAuthenticated()) {
            return Optional.empty();
        }

        return Optional.of(((UserDetailsImpl) authentication.getPrincipal()).getUser());
    }
}
// 4번
@EnableJpaAuditing(auditorAwareRef = "userAuditorAware") // auditorAware 의 빈이름을 넣어준다.
@SpringBootApplication
public class Application {

7️⃣ 필요한 부분만 갱신 ( Dynamic Insert/update )

✅ @DynamicInsert

이를 엔티티에 적용하게 되면 Insert 쿼리를 날릴 때 null 인 값은 제외하고 쿼리문이 만들어집니다.

@DynamicInsert
public class User {
  ...
}

✅ @DynamicUpdate

이를 엔티티에 적용하게 되면 Update 쿼리를 날릴 때 null인 값은 제외하고 쿼리문이 만들어집니다.

@DynamicUpdate
public class User {
  ...
}

[공통] 💥 적용 전/후 비교

더보기
적용전
적용 후

 

필드차이가 1개밖에 안되서 10ms 정도밖에 차이가 안나지만 필드가 많아질수록 더 차이가 많이납니다!

🚧 트랜잭션

2025.05.02 - [[내일배움캠프-Sparta]/Spring 6기 ( + TIL )] - 🚧 트랜잭션 (스탠다드)

 

🚧 트랜잭션 (스탠다드)

1️⃣ 현실에서의 트랜잭션💲 송금(계좌이체) 이것은 트랜잭션입니다.돈을 주는 사람 계좌는 금액이 차감되어야 하고, 돈을 받는 사람은 금액이 증가되어야 합니다.이 두 작업 중 어느 하나라

dimenshun.tistory.com

 

반응형

'[내일배움캠프-Sparta] > Spring 6기' 카테고리의 다른 글

📅 연관관계와 N+1 문제 💥  (0) 2025.05.05
🔍 영속성과 Transactional  (0) 2025.05.04
🚧 트랜잭션 (스탠다드)  (0) 2025.05.02
🔒 JWT 인증/인가  (0) 2025.05.01
TIL 49 [ JPA 심화 - ( MyBatis, ORM, 테이블 객체, Raw JPA 연관관계) ]  (0) 2025.05.01
'[내일배움캠프-Sparta]/Spring 6기' 카테고리의 다른 글
  • 📅 연관관계와 N+1 문제 💥
  • 🔍 영속성과 Transactional
  • 🚧 트랜잭션 (스탠다드)
  • 🔒 JWT 인증/인가
dimenshun
dimenshun
한 소년의 개발 일기
    반응형
  • dimenshun
    Dev Life Notes
    dimenshun
  • 전체
    오늘
    어제
    • 분류 전체보기 (268)
      • CS (23)
        • 자료구조 (0)
        • 알고리즘 (0)
        • 컴퓨터 구조 (8)
        • 네트워크 (6)
        • 운영체제 (3)
        • DB ( + SQLD ) (5)
        • SW공학 (1)
      • 프로그래밍 (3)
        • Java (0)
        • Spring (0)
        • HTML,CSS (3)
        • JavaScript (0)
      • 개발 툴 (7)
        • Git(버전관리) (1)
        • Docker (3)
        • AWS (2)
        • JSP (1)
      • 코딩테스트(Algorithm) (125)
        • 백준 (6)
        • 프로그래머스 (119)
      • [내일배움캠프-Sparta] (110)
        • Spring 6기 (106)
        • KPT 회고 (3)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    배포
    Java
    네트워크
    내일배움캠프
    CPU
    백엔드
    spring
    웹
    cs
    SQL
    network
    알고리즘
    세션
    컴퓨터구조
    운영체제
    개발자
    트랜잭션
    web
    OS
    Testcode
    SQLD
    코딩테스트
    메모리
    It
    AWS
    db
    docker
    Python
    Til
    KPT
  • hELLO· Designed By정상우.v4.10.3
dimenshun
TIL 50 [ JPA 심화 - ( 테이블 객체 다루는법, JpaRepository 쿼리,페이징처리, JPQL ,QueryDSL, Auditing, 필요한부분 갱신 ) , 트랜잭션 복습 ]
상단으로

티스토리툴바