🦁멋쟁이사자처럼 백엔드 부트캠프 13기 🦁
TIL 회고 - [50]일차
🚀50일차에는 이전에 JPA를 배웠다면 이번에는 Spring과 함께 쓰는 Spring Data JPA에 대해 배울 수 있었다.
학습 목표 : JPA를 활용하여 Spring에서 메소드를 활용하는 방법에 대해 실습
학습 과정 : 회고를 통해 작성
Spring Data JPA
- Spring프레임워크의 일부로 자바 개발자들이 관계형 DB의 데이터 접근을 용이하게 할 수 있도록 설계
- 복잡한 쿼리를 간단하게 처리, DB작업을 자동화하여 생산성을 향상시킴
- 주요 기능:
➡️리포지토리 추상화 : 리포지토리라는 개념을 사용해 CRUD연산을 위한 공통 인터페이스를 제공
➡️쿼리 메소드 생성 : 메소드 이름만으로 쿼리를 생성할수 있는 기능을 제공
= 즉 메소드의 이름을 분석하여 해당하는 SQL쿼리를 자동으로 생성함
➡️@Query 어노테이션 : 복잡한 SQL쿼리 또는 JPQL 쿼리를 메소드에 어노테이션으로 직접 정의할 수 있게 함
➡️페이징과 정렬 기능 제공
➡️명시적 트랜잭션 관리 : @Transactional 어노테이션을 사용하여 선언적 트랜잭션 관리 지원
= 이를 통해 데이터의 일관성과 무결성 유지 가능
Spring Data JDBC와 Spring Data JPA 는 모두 Spring Data 프로젝트의 일부로, 공통된 목적과 특징을 공유함.
❓Spring Data JDBC와 Spring Data JPA의 차이점
- Spring Data JDBC :
JDBC에 대한 더 간단하고 직접적인 접근을 제공하여 도메인 모델을 데이터베이스에 1:1로 매핑함
이는 JPA에 비해 가벼운 접근방식으로 애플리케이션의 성능을 최적화가능 - Spring Data JPA :
Java Persistence API를 사용하여 도메인 모델과 데이터베이스 간 복잡한 매핑과 데이터 관리를 처리함
이는 객체관계매핑(ORM)의 모든 장점을 제공. 하지만 설정과 구현이 복잡할 수 있음
엔티티 생성
@Entity
@Getter@Setter@ToString // JPA에서는 @ToString사용 시 주의할 것
@NoArgsConstructor
@Table(name = "jpa_user")
public class User {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
public User(String name, String email) {
this.name = name;
this.email = email;
}
}
Repository 생성
public interface UserRepository extends JpaRepository<User, Long> {...}
- UserRepository인터페이스는 JpaRepository를 상속받음
- JpaRepository는 이처럼 ListCrudRepository와 ListPagingAndSortingRepository 등을 상속받고 있으므로
- CRUD연산과 페이징 기능을 모두 제공해줄 수 있음
CommandLineRunner 테스트
@Bean
public CommandLineRunner run(UserRepository repository){
return args->{
// 테스트할 일
// CREATE
User user = new User("Dan", "Burn@premier.com");
repository.save(user);
log.info("[CREATE] 사용자 등록 : " + user.getName());
};
}
- 데이터 생성
// DELETE
User delUser = repository.findById(14L).get();
repository.delete(delUser);
- 데이터 삭제
// UPDATE
User updateUser = repository.findById(12L).get();
updateUser.setName("James");
updateUser.setEmail("McConnell@premier.com");
repository.save(updateUser);
- 데이터 수정
// SELECT
repository.findAll().forEach(user -> log.info(user.toString()));
- 데이터 조회
이름 기준 조회 메소드
public interface UserRepository extends JpaRepository<User, Long> {
// 이름 기준 조회 메소드 추가
List<User> findByName(String name);
}
- 테이블에는 동명이인이 존재할 수도 있으므로 findByName()을 User로 받지 않고 List타입으로 받아야함
(복수 이상일 수 있기때문)
// 이름 기준 조회 메소드 테스트
List<User> users = repository.findByName("Luke");
users.forEach(user -> log.info(user.toString()));
- Luke로 적어도 "Luke", "luke" 모두 검색될 수 있다.
- MySQL이 기본적으로 대소문자를 구분하지 않기때문에 대소문자를 구분하기 위해
1. 테이블 생성 시부터 제약조건을 넣을 수 있고
2. 쿼리문에 binary등의 조건을 넣어 구분해야할 수 있음
ex. select * from jpa_user where binary name = ‘Hong’;
➡️binary를 붙여 Hong만 검색이됨 (소문자 hong은 검색이 되지 않음)
❓이 외로 MySQL의 대소문자 구분 설정하는 방법
1. 컬럼의 컬레이션을 utf8_bin으로 변경하는 방법
ALTER TABLE user MODIFY COLUMN name VARCHAR(255) COLLATE utf8_bin;
- "Luke"과 "luke"을 구분하여 취급
2. BINARY 비교 사용
@Query("SELECT u FROM User u WHERE BINARY u.name = :name")
List<User> findByNameCaseSensitive(@Param("name") String name);
- JPQL, Native Query에서 BINARY를 활용해 대소문자를구분 가능
➡️컬레이션 설정과 상관없이 대소문자를 구분
3. COLLATE를 활용한 쿼리 실행
@Query("SELECT u FROM User u WHERE u.name COLLATE utf8_bin = :name")
List<User> findByNameCaseSensitive(@Param("name") String name);
- COLLATE utf8_bin을 사용하면 특정 쿼리에서만 대소문자 구별을 적용할 수 있습니다.
정리하자면
데이터베이스의 컬레이션을 처음부터 utf8_bin으로 설정하는 1번 방법이 가장 적절
❓다른 데이터베이스들은 대소문자를 구분하는지
➡️MariaDB와 MySQL은 보통 대소문자를 구분하지 않음
MariaDB : MySQL로 기반되어 동작방식이 거의 동일, 대소문자를 구분하지 않는 (Case-Insensitive) 특성
➡️PostgreSQL는 기본적으로 대소문자를 구분
대소문자를 구분하는(Case-Sensitive) 특성
이메일 기준 조회 메소드
// 이메일 기준 조회 메소드 추가
List<User> findByEmail(String email);
// 이메일 기준 조회 메소드 테스트
List<User> emails = repository.findByEmail("Chambers@premier.com");
emails.forEach(user -> log.info(user.toString()));
특정 이름을 포함하는 조회 메소드
// 특정 이름을 포함하는 조회 메소드 추가
List<User> findByNameContaining(String name);
- 이 메소드는 내부적으로
SQL (=Native Query) : SELECT * FROM user WHERE name LIKE '%입력값%';
JPQL : SELECT u FROM User u WHERE u.name LIKE %:name%
이러한 쿼리가 동작 - ➡️ex. findByNameContaining("Luke")
"PeterOLuke", "LuckyLukeLucky", "FeelLikeLuke"
Luke를 포함하는 데이터들이 모두 조회됨
// 특정 이름을 포함하는 조회 메소드 테스트
List<User> containName = repository.findByNameContaining("d"); // d가 포함된 이름 조회
containName.forEach(user -> log.info(user.toString()));
특정 이름으로 시작하는 조회 메소드
// 특정 이름으로 시작하는 조회 메소드 추가
List<User> findByNameStartingWith(String name);
// 특정 이름으로 시작하는 조회 메소드 테스트
List<User> startName = repository.findByNameStartingWith("Ja"); // Ja로 시작하는 이름 조회
startName.forEach(user -> log.info(user.toString()));
findByNameStartingWith(String name) | WHERE name LIKE '입력값%' |
findByNameEndingWith(String name) | WHERE name LIKE '%입력값' |
findByNameContaining(String name) | WHERE name LIKE '%입력값%' |
- 앞, 뒤, 앞뒤 등을 조회하는 메소드들
이름이 (?)이거나 이메일이 (?)인 데이터 조회 메소드 - OR 연산
// 이름이 (?)이거나 이메일이 (?)인 데이터 조회 메소드 추가
List<User> findByNameOrEmail(String name, String email);
// 이름이 (?)이거나 이메일이 (?)인 데이터 조회 메소드 테스트
List<User> nameOrEmail = repository.findByNameOrEmail("Andrew", "Berger@premier.com");
nameOrEmail.forEach(user -> log.info(user.toString()));
- 이름이 [Andrew] 이거나, 이메일이 [Berger@premier.com] 인 데이터 조회
이름이 (?) 이고, 이메일은 (?)인 데이터 조회 메소드 - AND 연산
// 이름이 (?) 이고, 이메일은 (?)인 데이터 조회 메소드 추가
List<User> findByNameAndEmail(String name, String email);
List<User> nameAndEmail = repository.findByNameAndEmail("Andrew", "Berger@premier.com");
nameAndEmail.forEach(user -> log.info(user.toString()));
- 이름은 [Andrew]이고, 이메일은 [Berger@premier.com]인 데이터를 조회해보면,
- 각각 다른 데이터를 의미하므로 And 연산에는 포함이 되지 않아 데이터가 조회되지않는다.
List<User> nameAndEmail = repository.findByNameAndEmail("Andrew", "Robertson@premier.com");
nameAndEmail.forEach(user -> log.info(user.toString()));
- And연산의 조건에 맞게 이메일을 Robertson@premier.com 으로 바꿔주어야
같은 데이터를 가리키므로 데이터가 조회된다.
고급 쿼리
- 복잡한 조건이나 다수의 조건을 조합해야할때는 쿼리 메소드만으로 부족할 수 있음
- @Query 어노테이션을 사용하여 JPQL이나 SQL쿼리를 직접 정의할 수 있음
- JQPL : 엔티티를 대상으로 쿼리를 수행
- SQL : 테이블을 대상으로 쿼리를 수행
고급 쿼리 - JPQL
// 고급 쿼리 생성 (JPQL)
@Query("SELECT u FROM User u Where u.name = :name")
List<User> advancedSelectUser(@Param("name") String name);
- @Param(”name”)
➡️@Param의 name이 JPQL에서의 :name에 매핑될 것
🚀파라미터 바인딩
1. 위치 기반 바인딩: ?1, ?2 등의 위치 기반 파라미터를 사용하여 쿼리에 파라미터를 바인딩
2. 이름 기반 바인딩: :name 과 같이 이름을 사용해 파라미터를 지정
(ex. 메소드 파라미터 위치에 @Param("name") 을 선언하여 매핑)
// 고급 쿼리 JPQL 테스트
repository.advancedSelectUser("Jayden").forEach(user -> log.info(user.toString()));
고급 쿼리 - JPQL (LIKE)
- 특정 문자를 포함하는 조건 사용
// 고급 쿼리 생성 (JPQL) - LIKE
@Query("SELECT u FROM User u WHERE u.name LIKE %:name%")
List<User> advancedSelectUserLike(@Param("name") String name);
// 고급 쿼리 JPQL - LIKE (%Ja% 로 Ja를 포함하는 것 조회)
repository.advancedSelectUserLike("Ja").forEach(user -> log.info(user.toString()));
수정 쿼리
- @Modifying 어노테이션을 사용하여 CRUD 연산을 적용해야함
➡️INSERT, UPDATE, DELETE는 데이터가 변경되어야하므로 @Modifying이 붙어있어야 데이터가 변경됨
// 고급 쿼리 - @Modifying
@Transactional
@Modifying
@Query("DELETE FROM User u WHERE u.email = :email")
int deleteByEmail(@Param("email") String email);
수정 쿼리 - 데이터 삭제
// 고급 쿼리 - 데이터 삭제 (@Modifying) 테스트
int count = repository.deleteByEmail("Chambers@premier.com");
log.info("삭제된 데이터 수 : " + count);
- 데이터를 삭제할 수 있음 >> 출력 : [삭제된 데이터 수 : 3]
- @Modifying 사용 시에는 @Transactional을 명시해야함
➡️즉 Spring Data JPA에서 @Modifying을 사용할 때 트랜잭션이 필요한 것
❓@Transactional 이 필요한 이유 (=트랜잭션이 필요한 이유)
➡️Spring에서는 기본적으로 조회 쿼리는 트랜잭션 없이 실행할 수 있지만,
수정/삭제 쿼리는 반드시 트랜잭션이 필요
➡️JPA는 기본적으로 트랜잭션이 없는 상태에서 데이터 변경을 수행할 수 없기 때문
수정 쿼리 - 데이터 수정
// 고급 쿼리 - @Modifying 데이터 수정
@Transactional
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id=:id")
int updateByEmail(@Param("id") Long id, @Param("email") String email);
// 고급 쿼리 - 데이터 수정 (@Modifying)
int updateCount = repository.updateByEmail(13L, "Koumas@premier.com");
log.info("수정된 데이터 수 : " + updateCount);
- 출력 : [수정된 데이터 수 : 1]
고급쿼리 - Native SQL
특정 이메일을 포함하는 데이터 조회
// 고급 쿼리 (SQL = nativeQuery) 사용 - 이메일로 데이터 조회
@Query(nativeQuery = true, value = "SELECT * FROM jpa_user WHERE email like %:email%")
List<User> selectByEmailNative(@Param("email") String email);
- ⚠️오류가 발생할 가능성이 있음
SQL문에서는 :email 앞뒤에 %가 붙는 것이 문법적으로 잘못될 수 있음
✅해결방법
@Query(nativeQuery = true, value = "SELECT * FROM jpa_user WHERE email LIKE CONCAT('%', :email, '%')")
List<User> selectByEmailNative(@Param("email") String email);
- 이처럼 CONCAT()함수로 ‘%’ 를 앞 뒤에 붙여주어야함
// 고급 쿼리 - native SQL 사용 - 이메일로 데이터 조회
repository.selectByEmailNative("premier").forEach(user -> log.info(user.toString()));
❓JPQL 사용과 Native Query (=SQL 직접 사용)의 차이점
JPQL (nativeQuery = false)
Native Query (nativeQuery = true)
SQL (DB 종속적) | JPQL (DB 독립적) | |
대상 | 테이블 (jpa_user) | 엔티티 (User) |
와일드카드 (%) | CONCAT('%', :email, '%') 필요 | LIKE %:email% 자동 변환 |
쿼리 동작 방식 | 직접 SQL 실행 | JPQL → SQL 변환 후 실행 |
활용 | JPQL로 표현하기 어려운 복잡한 SQL 실행 | DB 독립성 유지, 기본적인 CRUD 연산 수행 |
- 복잡한 조인이나 서브쿼리, 특정 DB함수를 사용할때는 JPQL의 한계를 느낄 수 있는데
- 이 때 @Query 어노테이션에 Native Query(=SQL)을 직접작성하는 것이 더 효과적일 수 있다.
🚀실습 - Spring Data JPA (Customer : Order = 1 : N 관계 테이블)
Customer
@Entity
@Getter@Setter@ToString
@NoArgsConstructor
@Table(name = "jpa_customer")
public class Customer {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Order> orders = new ArrayList<>();
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
}
- fetch = FetchType.EAGER
➡️@OneToMany는 기본적으로 FetchType.LAZY
여기서 EAGER로 설정해주어야 Customer를 불러온 후
그 Customer의 Order리스트를 가져오고자할때 함께가져와질것
Order
@Entity
@Getter@Setter@ToString
@NoArgsConstructor
@Table(name = "jpa_order")
public class Order {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String product;
private LocalDate date;
@ManyToOne
@JoinColumn(name = "customer_id")
private Customer customer;
public Order(String product, LocalDate date, Customer customer) {
this.product = product;
this.date = date;
this.customer = customer;
}
}
CustomerRepository
public interface CustomerRepository extends JpaRepository<Customer, Long> {...}
CommandLineRunner 테스트
@Bean
public CommandLineRunner run(CustomerRepository repository){
return args->{
repository.findAll().forEach(customer -> log.info(customer.getName()));
};
}
🚀오류 해결
⚠️getOrders() 메소드를 호출했을때 오류 발생
원인 : LAZY Loading (지연 로딩)이 발생했기때문
✅해결방법 : EAGER로 fetchType설정 변경
⚠️EAGER로 설정을 바꿔주는 것만으로는 모든 조회 시 항상 orders를 가져오게되므로 성능이 좋지 않음
✅개선방법 : Service 클래스 구현 + @Transactional 사용
Service클래스 안에서 구현되는 메소드에 @Transactional 어노테이션을 사용하여
트랜잭션의 시점 내에서 고객(Customer)를 얻어올때 Orders도 함께 얻어오게끔 구현
✅CustomerService
@Service
@RequiredArgsConstructor
@Slf4j
public class CustomerService {
private final CustomerRepository customerRepository;
@Transactional
public Customer getCustomer(Long id){
Customer customer = customerRepository.findById(id).get();
log.info("===getCustomer() 를 통해 getOrders()를 호출하는 시점입니다!===");
List<Order> orders = customer.getOrders();
orders.forEach(order -> log.info(order.getProduct()));
return customer;
}
}
- @Transactional
➡️getCustomer()메소드를 호출 시 트랜잭션이 동작 - 따라서 이 트랜잭션은 메소드 시작때 트랜잭션이 시작되고, 메소드가 끝나면 트랜잭션이 종료되는
메소드 생명주기와 같은 생명주기를 가짐
CommandLineRunner 테스트
@Bean
public CommandLineRunner run(CustomerRepository repository, CustomerService customerService){
return args->{
customerService.getCustomer(1L);
};
}
- getCustomer(1L)를 호출 시 [id=1]번에 해당하는 Customer를 불러오면서
- customerService의 getCusomter()메소드 수행에 따라 내부에서 구현된 getOrders()도 같이 수행됨
Service클래스를 구현하기 이전에는
Customer customer = repository.findById(1L).get();
List<Order> orders = customer.getOrders();
- Customer customer = repository.findById(1L).get();
➡️이때 트랜잭션이 이미 종료됨 - List<Order> orders = customer.getOrders();
getOrders() 메소드가 호출되는 시점에는 위의 findById()메소드에서 이미 트랜잭션이 종료되었으므로
LAZY LOADING (지연 로딩)이 일어나게되어 오류가 발생 (=customer로부터 가져올 데이터가 없음)
정리하자면
1. fetch = FetchType.EAGER로 바꿔주는 것만으로
Customer를 가져올때 getOrders()까지 불러올 수 있지만
Customer 엔티티를 조회할 때마다 즉시 orders 컬렉션도 함께 로딩되므로 EAGER는 성능 문제가 발생할 수 있음
2. 따라서 @Transactional을 적용하는 것이 더 적절한 방법
@Transactional을 사용하면 트랜잭션이 유지되는 동안 Lazy 로딩이 가능하기 때문에
FetchType.LAZY를 유지하면서도 정상적으로 데이터를 불러올 수 있음.
필요한 시점에 Lazy 로딩을 수행할 수 있는 것
따라서 성능 최적화를 고려하면 EAGER 대신 @Transactional을 적용하는 것이 적절
➡️Service클래스에서 @Transactional 어노테이션을 통해
Customer customer = customerRepository.findById(id).get();
List<Order> orders = customer.getOrders(); // Lazy 로딩 가능
- 이처럼 findById()메소드와 getOrders()메소드가 모두 같은 트랜잭션 내에서 동작할 수 있게만들어서
- getOrders()에서는 findById()로부터 얻어온 Customer의 주문리스트(orders)를 가져올 수 있는 것이다.
- LAZY 특성 상 지연 로딩이 되기때문에 @Transactional로
필요한 기능들을 같은 트랜잭션 내에서 처리되도록 구현하는 것
3. 이 외 방법으로 한시적인 트랜잭션 템플릿을 사용하는 방법이 있음
이로써 트랜잭션 내에서 테스트를 하도록 구현 가능
CommandLineRunner의 run() 메소드 파라미터에 (TransactionTemplate transactionTemplate)을 추가
// 2-2) 해결방법. TransactionTemplate 사용
transactionTemplate.execute(status -> {
Customer customer = repository.findById(1L).get();
log.info(customer.getName());
customer.getOrders().forEach(order -> log.info(order.getProduct()));
return null;
});
- 이 방법에서의 트랜잭션은 Spring Data JPA가 대신해주고 있음
- 예를 들어 Spring Data JPA가 findById()와 같은 메소드 호출 시
실제 구현체에서 메소드가 시작할때 트랜잭션 시작 (begin()), 메소드가 끝날때 commit()을 자동으로 해주었을 것
기존의 코드
Customer customer = repository.findById(1L).get();
List<Order> orders = customer.getOrders();
- findById()메소드가 이미 트랜잭션 시작과 종료를 모두 했기때문에
이 후 customer.getOrders()는 가져와질 수 없는 데이터인 것 - 따라서 LAZY 옵션이 동작되게 하기 위해서는 같은 트랜잭션에서 작업하게 구현해야한다.
🚀실습 - 다양한 조회 메소드
- 이메일에 특정 문자열을 포함하는 고객 조회
// 이메일에 특정 문자열을 포함하는 고객 조회
List<Customer> findByEmailContaining(String email);
// 특정 문자 포함하는 이메일 조회
List<Customer> containEmail = repository.findByEmailContaining("example");
containEmail.forEach((customer -> log.info("[example]을 포함하는 이메일 : " + customer.getEmail())));
- 각 고객과 고객의 주문수를 조회
// 각 고객과 고객의 주문 수를 조회
@Query("SELECT COUNT(o) FROM Order o WHERE o.customer.id = :id")
int findOrdersCountByCustomer(@Param("id") Long id);
// 고객의 주문수를 조회
int findOrdersCount = repository.findOrdersCountByCustomer(1L);
log.info("주문 수 : " + findOrdersCount);
- 고객의 세부정보와 그 고객의 가장 최근 주문 조회
// 고객의 세부 정보와 그 고객의 가장 최근 주문 조회
@Query("SELECT c, o FROM Customer c JOIN c.orders o " +
"WHERE o.date = (SELECT MAX(o2.date) FROM Order o2 WHERE o2.customer.id = c.id)")
List<Object[]> findRecentOrder(Long id);
- List<Object[]> : ➡️두 개 이상의 엔티티를 함꼐 조회해야하므로 Object[] 타입으로 받음
CommandLineRunner 테스트
List<Object[]> recentOrders = repository.findRecentOrder(1L);
for (Object[] result : recentOrders) {
Customer customer = (Customer) result[0]; // 첫 번째 값: Customer
Order order = (Order) result[1]; // 두 번째 값: 최근 Order
log.info("고객의 이름 : " + customer.getName());
log.info("고객의 이메일 : " + customer.getEmail());
log.info("고객의 나이 : " + customer.getAge());
log.info("이 고객의 가장 최근 주문 : " + order.getProduct());
}
- (“고객의 정보 : {}, {}, {}”, customer.getName(), customer.getEmail(), customer.getAge())
처럼 매핑 사용도 가능
- 평균 나이보다 많은 고객을 조회
// 평균 나이보다 많은 고객을 조회
@Query("SELECT c FROM Customer c WHERE c.age > (SELECT avg(c2.age) FROM Customer c2)")
List<Customer> findCustomersOlderThanAverage();
- 평균나이(30세)보다 많은 고객들이 조회되는 것을 확인할 수 있음
- 이메일에 특정 문자열을 포함하는 고객을 페이지를 통해 조회
// 이메일에 특정 문자열을 포함하는 고객 조회 (페이지 적용)
List<Customer> findByEmailContaining(String email, Pageable pageable);
// 페이지 객체 적용한 특정 문자 포함 이메일 조회
Pageable pageable = PageRequest.of(0, 3);
List<Customer> emailContainingPage = repository.findByEmailContaining("example", pageable);
emailContainingPage.forEach(customer -> log.info(customer.getEmail()));
- 첫번째 페이지에 3개의 데이터가 출력
- PageRequest.of(1, 3) 을 해주면, 두번째 페이지에 있는 4, 5, 6번째 데이터 3개가 출력될 것
🚀회고 결과 :
이번 회고에서는 단독으로 JPA를 사용하는 것이 아닌 Spring과 함께 사용하는 방법을 더 익힐 수 있었다.
Repository 인터페이스를 활용하여 메소드를 직접 정의하는 것과 스프링이 자동으로 제공해주는
findById()같은 메소드를 활용하는 방법을 계속해서 실습해보았다.
- SQL Query에 대한 다양한 실습 필요
- JOIN에 대한 추가 이해 필요
- 다양한 조건을 만들고 @Query로 쿼리를 만드는 연습
느낀 점 :
다양한 조건에서의 쿼리문을 수행해볼 수 있었는데 쿼리문에 아직 익숙치 않은 것 같았다.
alias로 바꾸고, 간단히 SELECT 조회는 할 수 있었지만 WHERE와 LEFT JOIN, JOIN 등에 대해서는 익숙치 않아서
쿼리문을 더 공부해봐야겠다고 생각했다.
향후 계획 :
- Rest API 알아보기
- Query 연습 문제 풀어보기
'Recording > 멋쟁이사자처럼 BE 13기' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_52일차_"RESTful API" (0) | 2025.02.21 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_51일차_"Criteria + hr DB" (0) | 2025.02.20 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_49일차_"JPA 상속 관계 매핑" (1) | 2025.02.18 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_48일차_"JPA 관계형 테이블" (1) | 2025.02.14 |
[멋쟁이사자처럼 부트캠프 TIL 회고] BE 13기_47일차_"JPA 엔티티 매핑" (0) | 2025.02.13 |