ํด๋น ๊ธ์ ๋ฐฐ์๋ณด์ Spring Data JPA ์๋ฆฌ์ฆ ์ ๋๋ค.
ํด๋น ์๋ฆฌ์ฆ์ ๋ด์ฉ์ด ์ด์ด์ง๋ ํํ์ด๋ฏ๋ก ๊ธ์ ๋ด์ฉ ์ค์ ์๋ต๋๋ ๋ง๋ค์ด ์์ ์ ์์ผ๋, ์์ธํ ์ฌํญ์ ์๋ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์!
- Spring Data JPA์ ๊ธฐ๋ณธ๊ณผ ํ๋ก์ ํธ ์์ฑ :: ์๋ฆฌ์ฆ ํ์ต ํ๊ฒฝ ์ค๋น
- JPA์ ๊ธฐ๋ณธ๊ณผ Spring Data JPA
- Springboot project ์์ JPA ์ค์ ํ๊ธฐ
- JPA์ ๊ธฐ๋ณธ ์ด๋
ธํ
์ด์
๋ค :: JPA์ ์์๊ณผ ๋์์ ๋
- ์ํฐํฐ์ ํ ์ด๋ธ ๋งคํ
- ํ๋์ ์ปฌ๋ผ ๋งคํ
- ๋งคํ ํ
์ด๋ธ๊ณผ ์ฐ๊ด๊ด๊ณ ๋งคํํ๊ธฐ :: RDB์ ๊ฝ, ์ฐ๊ด ๊ด๊ณ
- ์ฐ๊ด๊ด๊ณ๋?, ์ธ๋ ํค๋?, ๋งคํ ํ ์ด๋ธ์ด๋?
- ์ผ๋์ผ, ์ผ๋๋ค, ๋ค๋์ผ ์ฐ๊ด๊ด๊ณ
- ๊ณตํต ์ธํฐํ์ด์ค ๊ธฐ๋ฅ :: ์ด๋ป๊ฒ Data JPA ๋ ๋์ํ ๊น?
- ๋จ๊ฑด ์กฐํ ๋ฐํ ํ์
- ์ปฌ๋ ์ ์กฐํ ๋ฐํ ํ์
- ์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ ์ด์ฉํ๋ ๋ฐฉ๋ฒ :: ๋ด๊ฐ ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ JPA ์์ ๋ง๋ค์ด๋ณด์!
- ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
@Query
๋ฅผ ์ด์ฉํ์ฌ ๋ฉ์๋์ ์ ์ ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
- ํ์ด์ง๊ณผ ์ ๋ ฌ :: ๊ฒ์ํ๊ณผ ๊ฐ์ ํ์ด์ง๊ฐ ์๋ ์๋น์ค์์ ๋น์ ๋ฐํ๋ JPA ํ์ด์ง!
- Data JPA์ ํ์ด์ง๊ณผ ์ ๋ ฌ
- Web MVC ์์ JPA ํ์ด์ง๊ณผ ์ ๋ ฌ
- Auditing :: ๋ชจ๋ ์์ฒญ๊ณผ ์๋ต์
๋๊ฐ, ์ธ์
์ ๊ทผํ๋์ง ํ๋์ ์ํฐํฐ๋ก ๊ด๋ฆฌํ์.- ์์ JPA์ Auditing
- Spring Data JPA์ Auditing
- ๋ฐฐ์๋ณด์ Spring Data JPA ์๋ฆฌ์ฆ๋ฅผ ๋ง์น๋ฉฐ...
- ์๋ฆฌ์ฆ๋ฅผ ๋ง์น๋ฉฐ ๋๋์
- ๋ด๊ฐ ์ ๋ณด๋ฅผ ์ป์ ๊ณณ
- ํด๋น ์๋ฆฌ์ฆ๋ฅผ ์์ฃผํ์ จ๋์?
์์ ํฌ์คํ
์์ ์ฐ๋ฆฌ๋ JpaRepository
๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ CRUD ๋ฉ์๋์ ๋ํด์ ์์๋ณด์๋ค.
์ ๊น ์ธ๊ธํ๊ธฐ๋ ํ์๋๋ฐ, ๋ง์ฝ JpaRepository ๊ฐ ์ ๊ณตํ์ง ์๋ ๊ธฐ๋ฅ๋ค์ ์ฌ์ฉํด์ผํ ๋๋ ์ด๋ป๊ฒ ํ ๊น?
์๋ฅผ ๋ค์ด ์ฌ์ฉ์ PK๊ฐ ์๋ ์ด๋ฆ์ผ๋ก ์กฐํํ๊ณ ์ถ์ ๋, ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ์ด๋ฉ์ผ๋ก ์กฐํํ๊ณ ์ถ์๋, ๋์ด๊ฐ 20์ด ์ด์์ธ ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ณ ์ถ์ ๋ ๋ฑ๋ฑ..
๊ทธ๋ผ ์ด์ฉ ์ ์์ด ์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ
์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ๋? ๋ง ๊ทธ๋๋ก JPA๊ฐ ์๋์ผ๋ก ์์ฑํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋๊ฒ ์๋ ์ฌ์ฉ์๊ฐ ์ ์ํ ๋๋ก ์ฟผ๋ฆฌ๊ฐ ์์ฑ ํน์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ข ์์ ์ธ Native Query ๊ฐ ์์ฑ ๋๋ ๊ฒ์ ๋ง ํ๋ค.
JPA ์์ ์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์๋ ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๋ค.
- Named Query
- ์ฟผ๋ฆฌ ๋ฉ์๋
- @Query ์ด๋ ธํ ์ด์
๊ฐ ์กด์ฌํ๋ค.
๋ค์๋ ์ฟผ๋ฆฌ๋ ๋ง ๊ทธ๋๋ก ์ฟผ๋ฆฌ์ ์ด๋ฆ์ ๋ถ์ฌํ๋ ๋ฐฉ๋ฒ์ธ๋ฐ, ์ปดํ์ผ์ ํ์
์ฒดํฌ, ๊ฐ๋
์ฑ๊ณผ ๊ฐ์ ๋ถ๋ถ์์ ๋ฌธ์ ๊ฐ ์กฐ๊ธ ์๊ธฐ ๋๋ฌธ์ @Query
์ด๋
ธํ
์ด์
์ ์ฌ์ฉํ ์ฐ๋ฆฌ์๊ฒ๋ ๋น์ฅ ํ์ํ์ง๋ ์๋ค.
๊ทธ๋ฌ๋ฏ๋ก ์ฐ๋ฆฌ๋ 2๋ฒ 3๋ฒ, ์ฟผ๋ฆฌ ๋ฉ์๋์ @Query ์ ๋ ์ง์คํ๋ฉด ๋๋ค.
์ฟผ๋ฆฌ ๋ฉ์๋
์ฟผ๋ฆฌ ๋ฉ์๋๋ ๋ด๊ฐ ์๊ฐํ๋ JPA์์ ๊ฐ์ฅ ์ ๊ธฐํ ๊ธฐ์ ? ์ด๋ค.
๋๋ Intellij ๋ฅผ ์ฌ์ฉํ๋๋ฐ, Intellij ์์ Repository
์ธํฐํ์ด์ค ์ ๋ฉ์๋๋ฅผ ์ ์ํด๋ณด์.
Optional<User> find
๊น์ง๋ง ์ณ๋ ์๋์ ๊ฐ์ snippet ์ด ๋ฑ์ฅํ๋ค.
๋ญ๊น..?
๋์ถฉ ๊ฐ์ด ์กํ์ง ์๋๊ฐ?
๊ทธ๋ ๋ค. ๋ฉ์๋ ์ด๋ฆ์ผ๋ก ์ฐ๋ฆฌ๊ฐ ์ํ๋ ๊ธฐ๋ฅ์ ์ํํ ์ฟผ๋ฆฌ๊ฐ ์๋์ผ๋ก ์์ฑ๋๊ฒ ํ ์ ์๋ค.
์ฟผ๋ฆฌ ๋ฉ์๋ ๊ธฐ๋ฅ์ Spring Data JPA ์์ ์ ํด๋์ ๋ค์ด๋ฐ ์ปจ๋ฒค์ ์ ์งํค๋ฉด JPA๊ฐ ํด๋น ๋ฉ์๋ ์ด๋ฆ์ ๋ถ์ํด์ ์ ์ ํ JPQL ์ ๊ตฌ์ฑํ๋ค.
๋ํ์ ์ธ ํค์๋์ ๋ํด์ ์์๋ณด์๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- And
- Or
- Is, Equals
- Between
- LessThen
- After, Before
- IsNull
- OrderBy
- Not
์์ธํ ์ฌํญ์ Spring Data JPA-Query Method ๊ณต์ ๋ฌธ์ ๋ฅผ ์ฐธ๊ณ ํจ๋ฉด ์ด๋ค ํค์๋๋ก ๋ค์ด๋ฐ์ ํ์ ๋, ์ ์ ํ์ง ํ์ธํ ์ ์๋ค.
์ฟผ๋ฆฌ ๋ฉ์๋ Test
User
.classUserRepository
.interfaceUserRepositoryTest
.class
User ํด๋์ค
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String address;
private int age;
private LocalDateTime registeredDate;
}
๊ต์ก ํธ์๋ฅผ ์ํด ๋กฌ๋ณต๊ณผ ๊ด๋ จ๋ ์ด๋ ธํ ์ด์ ์ ์ ๊ฑฐํ์๋ค.
UserRepository ์ธํฐํ์ด์ค
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsernameAndAddress(String username, String address);
}
์ด๋ฒ ํ
์คํธ์ ํต์ฌ์ธ UserRepository
์ Query Method ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๋ ํฌ์งํ ๋ฆฌ์ด๋ค.
์ธํ ๋ฆฌ์ ์ด๋ฅผ ์ด๋ค๋ฉด snippet ์ ์ด์ฉํ ์ ์๋ค.
UserRepositoryTest ํด๋์ค
@SpringBootTest
@Transactional
@Rollback(false)
class UserRepositoryTest {
@Autowired
UserRepository userRepository;
@Test
@DisplayName("์ฌ์ฉ์ ์ด๋ฆ๊ณผ ์ฃผ์๋ก ์กฐํ")
void findUsernameAndAddressTest() {
// given
String username = "James";
String address = "seoul";
User user = User.builder()
.username(username)
.address(address)
.age(25)
.registeredDate(LocalDateTime.now())
.build();
userRepository.save(user);
// when
Optional<User> selectedUser = userRepository.findByUsernameAndAddress(username, address);
// then
selectedUser.ifPresentOrElse(
userOptional -> assertEquals(userOptional.getUsername(), username),
Assertions::fail
);
// then ๋๋ค๋ฅผ ์ฐ์ง ์์ ๊ฒฝ์ฐ
if(selectedUser.isEmpty()) {
fail();
}else {
User userOptional = selectedUser.get();
assertEquals(userOptional.getUsername(), username);
}
}
}
์ ๋ง ๋ฉ์ง๋ค..
ํ์ง๋ง ์์ ์ฟผ๋ฆฌ๋ฉ์๋ ๊ธฐ๋ฅ์ด ์๋ ๊ฐ๋ ฅํ๋ค๊ณ ํด๋ ๋ชจ๋ ์ฌ์ฉ์์ ๋์ฆ๋ฅผ ํ์ ํ๊ธด ํ๋ค๋ค.
์ด๋ค ํ๋ก์ ํธ์์๋ Native Query๋ฅผ ์ฌ์ฉํด์ผํ๋ ๊ณณ๋ ํ์ํ ๊ฒ์ด๊ณ , ๋ค์ํ ์กฐํฉ์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ์ง์ผํ๋ ์ํฉ์ด ์ฌ ๋๋ ์ด๋ป๊ฒ ํ ๊น?
@Query
๊ทธ๋ฐ ์ํฉ, ๊ฐ๋ฐ์๊ฐ ์ํ๋ ์ฟผ๋ฆฌ๋ฅผ ์ง์ ์ง์ผ ํ๋ ๊ทธ๋ฐ ์ํฉ์ด ์ฌ ๋ @Query
๋ ์์ฃผ ๊ฐ๋ ฅํ๋ค.
์ด๋ป๊ฒ ์ฌ์ฉํ ๊น?
@Query๋ ์คํํ ๋ฉ์๋ ์์ ์ ์ ์ฟผ๋ฆฌ๋ฅผ ์์ฑ ํ๋ค.
์ฌ๊ธฐ ๋ค์ด๊ฐ๋ ์ฟผ๋ฆฌ๋ JPQL ์ด๋ผ๋ ์ฟผ๋ฆฌ๊ฐ ๋ค์ด๊ฐ์ผ ํ๋ค.
JQPL
Java Persistence Query Language ์ธ JPQL์ ๊ฐ์ฒด์งํฅ ์ฟผ๋ฆฌ๋ก JPA๊ฐ ์ง์ํ๋ ๋ค์ํ ์ฟผ๋ฆฌ ๋ฐฉ๋ฒ ์ค ํ๋์ด๋ค.
๊ธฐ์กด์ SQL ์ค์ฌ์ ๊ฐ๋ฐ์ ์ต์ํ ์ฐ๋ฆฌ์๊ฒ๋ ์ด๋ป๊ฒ ๋ณด๋ฉด ๊ฐ์ฅ ๋จ์ํ ๋ฐฉ๋ฒ์ด๊ธฐ๋ ํ๋ค.
SQL๊ณผ JPQL์ ์ฐจ์ด์ ์ด ์๋ค๊ณ ํ๋ค๋ฉด
- SQL
- ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌ
- JPQL
- ์ํฐํฐ ๊ฐ์ฒด๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌ
๋ฅผ ํ๊ฒ ๋๋ค.
์ด๋ JPA๊ฐ ํ์ํ ์ด์ ์ธ ์ํผ๋์ค ๋ถ์ผ์น๋ฅผ ํด๊ฒฐํ๋ ค๋ ๋ ธ๋ ฅ์ ์ผํ ๊ฐ๋ค.
์ฟผ๋ฆฌ๋ฌธ ๋ด๋ถ์ ๋ค์๊ณผ ๊ฐ์ด ์ฐธ์กฐ๋ณ์.ํ๋
์ ๊ฐ์ ํํ๋ก ์ฌ์ฉ๋๋ค.
select
m.username,
m.address
from
Member m
where
m.age>18
JPQL ์ ๋ํด์๋ ๋ชจ๋ ๊ฒ์ ์ค๋ช ํ๊ธฐ ๋ณด๋ค ๊ฐ๋จํ๊ฒ ์ปจ์ ๋ง ์ด์ผ๊ธฐํ๋ ค ํ๋ค.
SQL์ ๋ชจ๋ ์๋ค๊ณ ๊ฐ์ ํด์ผ ํ๋ ์ผ๋จ์ ๋์ด๊ฐ๋๋ก ํ์ง๋ง ๊ผญ SQL ์ ๋ํด์ ์์๋ณด๊ณ JPQL์ ์์๋ณด๋ ๊ฒ์ ์ถ์ฒํ๋ค.
๋ง์ฝ ๋ณธ์ธ์ด SQL์ ๋ํด์ ํ๋๋ ๋ชจ๋ฅธ๋ค๋ฉด ํด๋น ๋ธ๋ก๊ทธ์ ์๋ฆฌ์ฆ์ธ ์ด๋ณด์๋ ์ค๋นํ๋ SQL ์ฝ๋ฉํ ์คํธ์์ ํ์ตํ ์ ์๋ค.
์ฝ๋ฉ ํ ์คํธ์ ์์ฃผ ๋์ค๋ SELECT๋ฅผ ์์ฃผ๋ก ์ค๋ช ํ๊ณ ์์ผ๋, ์์ ์ ๋ฌธ์ ๋ค๋ ์์ด์ SQL ์ ๋ฌธ์์๊ฒ๋ ์ข์ ๊ฒ ๊ฐ๋ค.
๋ค์ @Query ๋ก ๋์์์!
@Query๋ JpaRepository ๋ฅผ ์์ํ๋ ์ธํฐํ์ด์ค์์ ์ฌ์ฉํ๋ค.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("์ฟผ๋ฆฌ๋ฌธ")
List<User> methodName();
์ด์ ๊ฐ์ ํํ๋ก ์ฃผ๋ก ์ฌ์ฉ๋๋ค.
๋ง์ฝ age๊ฐ 20์ด ์ด์์ธ ์ฌ๋์ ์กฐํํ๋ค๊ณ ํด๋ณด๋ฉด JPQL์ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉ๋ ๊ฒ์ด๋ค.
Sring jpql = "select u from User u where u.age > 20";
์ค์ํ ๊ฒ์ ์ฐ๋ฆฌ๋ ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฅผ ๋ ๋ฆฌ๋๊ฒ ์๋๋ผ ์ํฐํฐ๋ฅผ ๋์์ผ๋ก ๋ ๋ฆฐ๋ค๋ ๊ฒ์ด๋ค.
ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ ์ํค๊ธฐ
์ฐ๋ฆฌ๊ฐ ์ด๊ฑธ ํ๋ ์ด์ ๋ ๋ญ๊น?
๋ฐ๋ก ์ฌ์ฉ์ ์ ์ ์ฟผ๋ฆฌ๋ฅผ ํ๋ ๊ฒ์ด๋ค.
์ฆ, ์๋์ methodName()
์ ๋ค์ด๊ฐ ํ๋ผ๋ฏธํฐ๋ฅผ ์ฐ๋ฆฌ์ ์ฟผ๋ฆฌ๋ฌธ์ ๋ฃ๋ ๊ฒ์ด๋ค.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("์ฟผ๋ฆฌ๋ฌธ")
List<User> methodName();
์ด๋ฅผ ํ๋ฆฌ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ด๋ผ๊ณ ํ๋ค.
ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์๋ค.
- ์์น ๊ธฐ๋ฐ
- ์ด๋ฆ ๊ธฐ๋ฐ
์์น ๊ธฐ๋ฐ์ ์ฐ์ง๋ง์, ์ด๋ฆ ๊ธฐ๋ฐ์ ์ฐ์
์์น ๊ธฐ๋ฐ์ JDBC ํ๋ก๊ทธ๋๋ฐ์ ํ ๋์ ResultSet ์ ์๊ฐํ๋ฉด ์ฌ์ธ ๊ฒ ๊ฐ๋ค.
์ด๋ฆ ๊ธฐ๋ฐ์ ์ฐ๋ผ๊ณ ํ๋ ์ด์ ๋ JPA ์ ์ ๊ตฌ์? ๊น์ํ ๊ฐ๋ฐ์๋๋ ๊ฐ๋ ์ฑ์ ์ํด ์ด๋ฆ ๊ธฐ๋ฐ์ ์จ๋ผ! ๋ผ๊ณ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
๋๋ 100% ๋์ํ๋ค.
๊ทธ๋ฌ๋ ์ฐ๋ฆฌ๋ ์ด๋ฆ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉํ๋๋ก ํ์ ใ ใ
์ด๋ฆ ๊ธฐ๋ฐ์ผ๋ก ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ํ๊ณ ํ๋ผ๋ฏธํฐ์ @Param("")
์ด๋
ธํ
์ด์
์ผ๋ก ๋ฉ์๋์ ๋ค์ด์ค๋ ํ๋ผ๋ฏธํฐ๊ฐ ์ด๋ค ์ด๋ฆ์ผ๋ก ์ง์ ๋ ์ง ์ ํ ์ ์๋ค.
public interface UserRepository extends JpaRepository<User, Long> {
@Query("select u from User u where u.username = :name")
List<User> methodName(@Param("name") String username);
์ด์ @Query
์ ๋ํด์ ์ด๋ ์ ๋ ๋ฐฐ์ด ๊ฒ ๊ฐ์ผ๋ ํ
์คํธ๋ฅผ ๋ง์ง๋ง์ผ๋ก ๊ธ์ ๋ง์น๋ ค ํ๋ค.
@Query ํ ์คํธ
User
.classUserRepository
.interfaceUserRepositoryTest
.class
User
UserRepository
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.username LIKE %:char% and u.age > :maxAge")
List<User> findByLetterWithConditions(@Param("char") char letter,
@Param("maxAge") int age);
}
JPQL ๋ก ์ด๋ฆ ๊ธฐ๋ฐ ํ๋ผ๋ฏธํฐ ๋ฐ์ธ๋ฉ์ ํ ๊ฒ์ ๋ณผ ์ ์๋ค.
UserRepositoryTest
@SpringBootTest
@Transactional
@Rollback(false)
class UserRepositoryTest {
@Autowired
UserRepository userRepository;
@Test
void findUsernameAndAddressTest() {
// given
String jamesUsername = "James";
String maryUsername = "Mary";
User james = User.builder()
.username(jamesUsername)
.age(25)
.build();
User mary = User.builder()
.username(maryUsername)
.age(30)
.build();
userRepository.save(james);
userRepository.save(mary);
// when
List<User> optionalUsers = userRepository.findByLetterWithConditions('a', 23);
// then
assertEquals(optionalUsers.get(0).getUsername(), jamesUsername);
assertEquals(optionalUsers.get(1).getUsername(), maryUsername);
}
}
๋๊ธ