Bibi's DevLog ๐ค๐
[Spring] ์ธํ๋ฐ ์คํ๋ง ์ ๋ฌธ(๊น์ํ ๋) - ์์JDBC, ํตํฉํ ์คํธ, ์คํ๋งJdbcTemplate, JPA, ์คํ๋ง ๋ฐ์ดํฐ JPA ๋ณธ๋ฌธ
[Spring] ์ธํ๋ฐ ์คํ๋ง ์ ๋ฌธ(๊น์ํ ๋) - ์์JDBC, ํตํฉํ ์คํธ, ์คํ๋งJdbcTemplate, JPA, ์คํ๋ง ๋ฐ์ดํฐ JPA
๋น๋น bibi 2021. 3. 6. 00:33์ด ๊ธ์ ์ธํ๋ฐ ๊น์ํ ๋์ ์คํ๋ง ์ ๋ฌธ ๊ฐ์ ๋ฅผ ๋ฃ๊ณ ์ ๋ฆฌํ ๊ธ์ ๋๋ค.
(์ด์ด์)
์คํ๋ง DB ์ ๊ทผ ๊ธฐ์
...
์์ JDBC
์ ํ๋ฆฌ์ผ์ด์ ๊ณผ DB๋ฅผ ์ฐ๊ฒฐํด ์ ์ฅํ๋ ๋ฒ์ ๋ฐฐ์๋ณผ ๊ฒ์ด๋ค.
์ฟผ๋ฆฌ๋ฌธ์ ์ด์ฉํด DB์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๊ณ ๋นผ๋ ๋ฐฉ๋ฒ
๊ทธ ์ค์์๋ 20๋ ์ ๋ฐฉ์์ธ ์์ JDBC API๋ง์ ์ฌ์ฉํ ๋ฐฉ๋ฒ์ ๋จผ์ ๋ณด๊ณ , ์ ์ฐจ ๋ฐ์ ๋๋ ๊ธฐ์ ๋ค์ ๋ฐฐ์ธ ๊ฒ์ด๋ค.
์๋ฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก DB์ ์ฐ๊ฒฐํ๋ ค๋ฉด JDBC๋ผ๋ ๋๋ผ์ด๋ฒ๊ฐ ๊ผญ ์์ด์ผ ํ๋ค.
1
build.gradle
์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
dependencies {
// codes ...
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
runtimeOnly 'com.h2database:h2'
}
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
: DB์ ์ฐ๊ฒฐ์ ์ํด, JDBC ๋๋ผ์ด๋ฒ ์ถ๊ฐruntimeOnly 'com.h2database:h2'
: DB์ ์ฐ๊ฒฐํ ๋, DB๊ฐ ์ ๊ณตํ๋ ํด๋ผ์ด์ธํธ๊ฐ ํ์ํด์ ์ถ๊ฐํ ์ฝ๋
application.properties
์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค. (src/main/java/resources/application.properties
)
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
url
: ์ ๊ทผํ DB์ ๊ฒฝ๋ก. H2๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ํ ๋ ๋ฃ์๋ ์ฃผ์์ ๋์ผํ๋ค.driver-class-name
: ์ ๊ทผํ DB๋๋ผ์ด๋ฒ์ ์ด๋ฆimportํ์ง ์์ผ๋ฉด ๋นจ๊ฐ์์ด๋ค.
build.gradle
์ ๊ฐ์ ์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ ๋ฒํผ์ ๋๋ฅด๋ฉด ํด๊ฒฐ๋๋ค.
spring.datasource.username=sa
: ์คํ๋ง๋ถํธ2.4๋ถํฐ๋ ์ด๋ฅผ ์ถ๊ฐํ์ง ์์ผ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค(Wrong user name or password
). ๊ณต๋ฐฑ์ด ์ ํ ์์ด์ผ ํ๋ค.
2
์ด์ JDBC๋ก ์ฐ๊ฒฐ๋ ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ๋ง๋ค์ด ๋ณธ๋ค. ๊ธฐ์กด MemoryMemberRepository๊ฐ ์๋ ์๋ก์ด ๊ตฌํ์ฒด JdbcMemberRepository๋ฅผ ๋ง๋ ๋ค.
์๋ ๊ธฐ์ ์ด๋ฏ๋ก ์ด๋ ๊ฒ ํ์๊ตฌ๋~ ์ ๋๋ง ์๊ณ ์ฐธ๊ณ ๋ง ํ์!!
JdbcMemberRepository.java
(src/main/java/ํ๋ก์ ํธ๋ช
/repository
์ ์์ฑ)
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.jdbc.datasource.DataSourceUtils;
import javax.sql.DataSource;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class JdbcMemberRepository implements MemberRepository{
private final DataSource dataSource;
public JdbcMemberRepository(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public Member save(Member member) {
String sql = "insert into member(name) values(?)";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pstmt.setString(1, member.getName());
pstmt.executeUpdate();
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
member.setId(rs.getLong(1));
} else {
throw new SQLException("id ์กฐํ ์คํจ");
}
return member;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findById(Long id) {
String sql = "select * from member where id = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setLong(1, id);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
} else {
return Optional.empty();
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public List<Member> findAll() {
String sql = "select * from member";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery();
List<Member> members = new ArrayList<>();
while(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
members.add(member);
}
return members;
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
@Override
public Optional<Member> findByName(String name) {
String sql = "select * from member where name = ?";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
rs = pstmt.executeQuery();
if(rs.next()) {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return Optional.of(member);
}
return Optional.empty();
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
close(conn, pstmt, rs);
}
}
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
private void close(Connection conn, PreparedStatement pstmt, ResultSet rs)
{
try {
if (rs != null) {
rs.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (pstmt != null) {
pstmt.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null) {
close(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
DataSource
: DB์ ์ฐ๊ฒฐํ๊ธฐ ์ํด ํ์ํ ์์. importํ๋ค (javax.sql.DataSource
)datasource๋ ์คํ๋ง์ผ๋ก๋ถํฐ ์ฃผ์ ๋ฐ์์ผ ํ๋ค.
์คํ๋ง์ด
application.properties
์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก DataSource๋ฅผ ๋ง๋ค์ด ๋๋๋ฐ, ์ด๋ฅผ ์ฃผ์ ๋ฐ์์ผ ํ๋ค.
Connection connection = dataSource.getConnection(sql)
DB๋ก๋ถํฐ์ ์ปค๋ฅ์ (์์ผ)์ ์ป์ ์ ์๋ค.
์ด์ ์ฌ๊ธฐ์ SQL๋ฌธ์ ๋ง๋ค์ด DB์ ์ ๋ฌํ๋ฉด ๋๋ ๊ฒ์ด๋ค.
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)
Connection์ ๋ฃ์ SQL๋ฌธ์ ์ค๋นํ๋ค.
Statement.RETURN_GENERATED_KEYS
: ์ต์ . DB์์ ์๋์ผ๋ก ์์ฑ๋ ํค๋ฅผ ๊ฐ์ ธ์จ๋ค.DB์์
generated by default as identity
๋ก ์์ฑ๋๋ ๊ฐ, ์ฆid
๊ฐ์ ๋งํจ.
pstmt.setString(1, member.getName());
1, member.getName()
: sql๋ฌธ์ ์ฒซ ๋ฒ์งธ ์์ผ๋์นด๋(?
)๋ฅผmember.getName()
์ผ๋ก ์ ํ ํ๋ค.
pstmt.executeUpdate();
- DB์ ์ ์ฅํ๊ธฐ ์ํด DB์ sql์ฟผ๋ฆฌ๋ฌธ์ ๋ณด๋ธ๋ค.
rs = pstmt.getGeneratedKeys();
Statement.RETURN_GENERATED_KEYS
๋ก ์ป์ ํค ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
try-catch๋ฌธ์ผ๋ก Exception์ ์ก์ ์ฃผ์ด์ผ ํ๋ค.
close(conn, pstmt, rs)
: ์ฌ์ฉํ ์์ (Connection, PreparedStatement, ResultSet)์ ๋ฆด๋ฆฌ์ฆํด์ค์ผ ํ๋ค.โ ๋คํธ์ํฌ๋ฅผ ํตํด ์ด์ฉํ ์์๋ค์ ์ฌ์ฉ ํ ๋ฐ๋์ ๋ฆฌ์์ค๋ฅผ ๋ฐํํด์ผ ํ๋ค.
๊ทธ๋ ์ง ์์ผ๋ฉด ์์์ด ๊ณ์ ์์ฌ์ ์๋น์ค ์ฅ์ ๊ฐ ๋ ์ ์๋ค.
pstmt.executeQuery();
- DB๋ฅผ ์กฐํํ ๋๋
executeQuery()
๋ฅผ ์ฌ์ฉํ๋ค.
- DB๋ฅผ ์กฐํํ ๋๋
์คํ๋ง์ผ๋ก DB ์ปค๋ฅ์ ์ฐ๊ฒฐ ๋ฐ ํด์ ํ๊ธฐ
private Connection getConnection() {
return DataSourceUtils.getConnection(dataSource);
}
์คํ๋ง ํ๋ ์์ํฌ๋ฅผ ํตํด DB ์ปค๋ฅ์ ์ ์ป์ ๋๋ ๋ฐ๋์ ์ด๋ ๊ฒ ์ป์ด์ผ ํ๋ค. (ํ์)
DataSourceUtils
๋ฅผ ํตํดgetConnection()
์ ์ฌ์ฉํด์ผ ํ๋ค.๋ฐ์ดํฐ๋ฒ ์ด์ค ํธ๋์ญ์ ์ด ๋ฐ์ํ์ง ์๊ณ ๋๊ฐ์ ์ปค๋ฅ์ ์ ์ ์งํ๋๋ก ๋์ ์ค๋ค.
private void close(Connection conn) throws SQLException {
DataSourceUtils.releaseConnection(conn, dataSource);
}
- ๋ง์ฐฌ๊ฐ์ง๋ก ์คํ๋ง ํ๋ ์์ํฌ๋ฅผ ํตํด DB ์ปค๋ฅ์
์ ๋ซ์ ๋๋ ๋ฐ๋์ ์ด๋ ๊ฒ ๋ซ์์ผ ํ๋ค.
DataSourceUtils
๋ฅผ ํตํดreleaseConnection()
์ ์ฌ์ฉํด์ผ ํ๋ค.
3
์ด์ JDBC๋ก ์ฐ๊ฒฐ๋๋๋ก ๋ฆฌํฌ์งํ ๋ฆฌ๋ฅผ ์์ฑํ์ผ๋ฏ๋ก ์ฌ์ฉํด ๋ณด์.
SpringConfig.java
(src/main/java/ํ๋ก์ ํธ/service/SpringConfig
)
์๋์ ๊ฐ์ด ์์ ํ๋ค.
package hello.hellospring;
import hello.hellospring.repository.JdbcMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class SpringConfig {
private DataSource dataSource;
@Autowired
public SpringConfig(DataSource dataSource) {
this.dataSource = dataSource;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
return new JdbcMemberRepository(dataSource);
}
}
- DataSource
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์ ์ ํ๋ํ ๋ ์ฌ์ฉํ๋ ๊ฐ์ฒด.
- ์คํ๋ง์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ปค๋ฅ์
(
application.properties
) ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก, ์์ฒด์ ์ผ๋ก DataSource๋ฅผ ์คํ๋ง ๋น์ผ๋ก ์์ฑํด ์ค๋ค. ์ด๋ฅผ ์์ฑ์๋ก ์ฃผ์ ํด ์ฌ์ฉํ๋ค(DI).@Autowired DataSource dataSource
๋ก ํด๋ ๋์ง๋ง, DI๋ก ํ๋ ๊ฒ์ด ๋ ์ข์ ๋ฐฉ๋ฒ.
โ ๋ค๋ฅธ ์ด๋ค ์ฝ๋๋ ๋ณ๊ฒฝํ์ง ์๊ณ , ์ธํฐํ์ด์ค๋ฅผ ํ์ฅํด JdbcMemberRepository.java
๋ฅผ ๋ง๋ค๊ณ SpringConfig.java
๋ง ์์ ํ๋ค.
4
์ด์ ์์ ๋ DB๋ฅผ ๋ค์ ํ์ธํด ๋ณธ๋ค.
- H2๋ฅผ ์คํํ๊ณ , ์คํ๋ง ์ฑ์ ์คํํ ๋ค,
localhost:8080
์ผ๋ก ์ ์ํด ํ์ ๋ชฉ๋ก์ ์กฐํํ๋ค.- H2 DB์ ์ ์ฅํ๋ ๋ด์ญ์ด ์กฐํ๋๋ฉด ์ฑ๊ณต์ด๋ค.
- ์ด๋ฒ์๋ ํ์ ๊ฐ์
์ผ๋ก ํ์์ ์ถ๊ฐํ ๋ค ๋ค์ ํ์ ๋ชฉ๋ก์ ์กฐํํ๋ค.
- ์ถ๊ฐํ ํ์์ด ๋ชฉ๋ก์ผ๋ก ์กฐํ๋๋ฉด ์ฑ๊ณต์ด๋ค.
์ด์ ๋ฐ์ดํฐ๋ฅผ DB์ ์ ์ฅํ๋ฏ๋ก, ์คํ๋ง ์๋ฒ๋ฅผ ๊ป๋ค ์ผ๋ ๋ฐ์ดํฐ๊ฐ ์์ ํ๊ฒ ์ ์ฅ๋๋ค.
(ํต์ฌ) ์ค์ํ ๋ถ๋ถ!
์คํ๋ง์ ์ ์ฐ๋๊ฐ?? ๊ฐ์ฒด์งํฅ์ด ์ข์์.
โ ๊ธฐ์กด์ ๋ค๋ฅธ ์ด๋ค ์ฝ๋๋ ๋ณ๊ฒฝํ์ง ์๊ณ , ์ธํฐํ์ด์ค๋ฅผ ํ์ฅํด
JdbcMemberRepository.java
๋ฅผ ๋ง๋ค๊ณSpringConfig.java
๋ง ์์ ํ๋ค.
์ฆ ๊ฐ์ฒด์งํฅ์ ๋คํ์ฑ์ ํธํ๊ฒ ํ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
์ธํฐํ์ด์ค(MemberRepository
)๋ฅผ ๋๊ณ , ์๋ก ๋ค๋ฅธ ๊ตฌํ์ฒด(MemoryMemberRepository
๋๋ JdbcMemberRepository
)๋ค์ ์ฝ๊ณ ํธํ๊ฒ ๋ฐ๊ฟ ๋ผ์ธ ์ ์๋ค.
๋, DI๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๋ค๋ฅธ ์์ค์ฝ๋๋ค์ ๋ด์ฉ์ ์ ํ ์ ๋ ํ์๊ฐ ์์ด์ก๋ค.
์คํ๋ง์ ์ด ๊ณผ์ ๋ค์ด ๋ ํธํ๋๋ก ์ง์ํด ์ค๋ค.
- MemberService๋ MemberRepository๋ฅผ ์์กดํ๊ณ ์๋ค.
- MemberRepository๋ ๊ทธ ๊ตฌํ์ฒด๋ก MemoryMemberRepository์ JdbcMemberRepository๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
๋ฐ๋ ์คํ๋ง ์ค์ ์ ์์ ๊ฐ๋ค.
- ๊ธฐ์กด์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ MemoryMemberRepository๋ฅผ ์ญ์ ํ๊ณ ,
- ์๋ก memberRepository๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํ๋ค.
- ๊ทธ ์ธ์ ๋๋จธ์ง ์ฝ๋๋ ์ ๋ ๊ฒ ์๋ค.
์ด๋ฅผ SOLID์์น ์ค '๊ฐ๋ฐฉ-ํ์ ์์น(OCP, Open-Closed Principle)'์ด๋ผ๊ณ ํ๋ค. (SOLID๋ ๋์ค์ ๊ฒ์ํด ๋ณด๋ผ)
ํ์ฅ์๋ ์ด๋ ค ์๊ณ , ์์ (๋ณ๊ฒฝ)์๋ ๋ซํ ์๋ค.
ํ์ฅ(๊ธฐ๋ฅ์ถ๊ฐ)์ ํ๋ ค๋ฉด ์์ ์ ํด์ผ ํ๋๋ฐ ์ด๋ป๊ฒ ์ด๊ฒ ๊ฐ๋ฅํ๊ฐ?
์ฐ๋ฆฌ๊ฐ ํ ๊ฒ์ฒ๋ผ, ๊ฐ์ฒด์งํฅ์ ๋คํ์ฑ์ ์ ํ์ฉํ๋ฉด ๊ธฐ๋ฅ์ ์์ ํ ๋ณ๊ฒฝํ๋ฉด์๋ ๊ธฐ์กด ์ฝ๋ ์ ์ฒด๋ฅผ ๋ณ๊ฒฝํ์ง ์์๋ ๋๋ค.
(๋ฌผ๋ก ์๋ก์ด ๊ธฐ๋ฅ์ ์กฐ๋ฆฝํ๋ ์ฝ๋ ์ ๋๋ ์์ ํด์ผ ํ๋ค)
์คํ๋ง์ DI๋ฅผ ์ฌ์ฉํ๋ฉด, "๊ธฐ์กด ์ฝ๋๋ฅผ ์ ํ ์๋์ง ์๊ณ , ์ค์ ๋ง์ผ๋ก ๊ตฌํ ํด๋์ค๋ฅผ ๋ณ๊ฒฝ"ํ ์ ์๋ค. ์ด๊ฒ์ด ๊ฐ์ฒด์งํฅ์ ๋งค๋ ฅ ์ค ํ๋์ด๋ค.
์คํ๋ง ํตํฉ ํ ์คํธ
์คํ๋ง ์ปจํ ์ด๋์ DB๊น์ง ์ฐ๊ฒฐ๋ ''ํตํฉ ํ ์คํธ''๋ฅผ ์งํํด ๋ณด์.
์ง๊ธ๊น์ง ํ๋ ํ ์คํธ์ฝ๋๋ ์คํ๋ง์ ์ฌ์ฉํ์ง ์๊ณ , ์์ํ ์๋ฐ ์ฝ๋๋ง ๊ฐ์ง๊ณ ๋ง๋ ๊ฒ์ด๋ค.
ํ์ง๋ง ์ด์ ์คํ๋ง์ด ๊ด์ฌํด์ ๋ฐ์ดํฐ์์ค ๋ฑ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์, ํ ์คํธ์ฝ๋๋ ์คํ๋ง์ ์ฌ์ฉํ๋๋ก ์์ ํด์ผ ํ๋ค.
(์๋๋ ๋ณดํต TEST ์ ์ฉ DB๋ฅผ ์ฐ๊ฑฐ๋, ๋ก์ปฌ DB๋ฅผ ์ฐ๊ฒฐํ ๋ค์ ์งํํ๋ค.)
MemberServiceIntegrationTest.java
๋ผ๋ ์๋ก์ด ํ ์คํธํด๋์ค๋ฅผ ๋ง๋ค์ด ์งํํ๋ค.
(src/test/java/ํ๋ก์ ํธ/service/MemberServiceIntegrationTest.java
)
package hello.hellospring.service;
import hello.hellospring.domain.Member;
import hello.hellospring.repository.MemberRepository;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class MemberServiceIntegrationTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
void ํ์๊ฐ์
() {
// given - ํ
์คํธํ ๋ฐ์ดํฐ
Member member = new Member();
member.setName("spring");
// when - ๊ฒ์ฆํ๋ ค๋ ๋ก์ง : MemberService์ join().
Long saveId = memberService.join(member);
// then - ํ
์คํธ ๊ฒฐ๊ณผ (๊ฒ์ฆ๋ถ)
Member findMember = memberService.findOne(saveId).get();
assertThat(member.getName()).isEqualTo(findMember.getName());
}
@Test
void ์ค๋ณตํ์์์ธ() {
Member member1 = new Member();
member1.setName("spring");
Member member2 = new Member();
member2.setName("spring");
memberService.join(member1);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> memberService.join(member2));
assertThat(e.getMessage()).isEqualTo("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
}
}
@SpringBootTest
- ์คํ๋ง ์ปจํ ์ด๋์ ํ ์คํธ๋ฅผ ํจ๊ป ์คํํ๋ค. (์คํ๋ง์ ์ง์ ๋์์ ํ ์คํธํ๋ค)
@Transactional
ํ ์คํธ์ผ์ด์ค์ ๋ถ์ด๋ฉด, ํ ์คํธ ์คํ์ ํธ๋์ญ์ ์ ๋จผ์ ์คํํ๊ณ ํ ์คํธ๊ฐ ๋๋๋ฉด ๋กค๋ฐฑ์ ํด ์ค๋ค.
์ด๋ ๊ฒ ํ๋ฉด DB์ ๋ฐ์ดํฐ๊ฐ ๋จ์ง ์์ผ๋ฏ๋ก ๋ค์ ํ ์คํธ์ ์ํฅ์ ์ฃผ์ง ์๋๋ค.
์ผ๋ฐ ํด๋์ค์ ๋ถ์ด๋ฉด ์คํ ์ ํธ๋์ญ์ ๋ง ์คํํ๋ค. (๋กค๋ฐฑ์ ํ์ง ์๋๋ค)
ํ ์คํธ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ฐ๋ณต ์คํ ๊ฐ๋ฅํด์ผ ํ๋ค. ๊ทธ๋ฐ๋ฐ DB์ ํ ์คํธ ๋ฐ์ดํฐ๊ฐ ๊ณ์ ์์ด๋ฉด ๊ฐ์ ํ ์คํธ๋ฅผ ๋ฐ๋ณตํ์ ๋ ์ค๋ณต ๋ฐ์ดํฐ๋ก ํ ์คํธ๊ฐ ์ ํํ์ง ์๊ฒ ๋๋ค.
์คํ๋ง์ด
@BeforeEach
,@AfterEach
๋์ ํธ๋์ญ์ ์ ์ฌ์ฉํด ์ด๋ฅผ ํด๊ฒฐํด ์ฃผ์๋ค.- ํธ๋์ญ์ (Transcation) : ๋กค๋ฐฑ? - ๊ณต๋ถํ๊ธฐ..
๋ฐ๋๋ก
@Commit
์ ๋ถ์ธ ํ ์คํธ๋ ๋กค๋ฐฑํ์ง ์๊ณ ๋ณ๊ฒฝ๋ด์ฉ์ DB์ ๋ฐ์ํ๋๋ก ์ค์ ํ ์ ์๋ค.
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
- ํ ์คํธ์ฝ๋์ด๋ฏ๋ก DI๋ ์๋ตํ๊ณ ๊ณง๋ฐ๋ก ์ฐ๊ฒฐํด ์ค๋ค.
- MemoryMemberRepository๊ฐ ์๋, ์ธํฐํ์ด์ค์ธ MemberRepository๋ก ๋ฐ๊พผ๋ค.
๋จ์ ํ ์คํธ
์คํ๋ง์ ๋์ฐ์ง ์๊ณ ์๋ฐ ์ฝ๋๋ก๋ง ์งํํ๋ฉฐ ๊ฐ์ฅ ์์ ๋จ์์ ํ
์คํธ๋ก๋ง ์งํํ๋ ๊ฒ์ ''๋จ์ํ
์คํธ''๋ผ๊ณ ํ๋ค. (์ด์ ์ ํ๋ MemberServiceTest.java
์ฒ๋ผ)
๋ฐ๋๋ก ์คํ๋ง๋ ๋์ฐ๊ณ DB๋ ์ฐ๋ํด ์งํํ๋ ํ
์คํธ๋ฅผ ''ํตํฉํ
์คํธ''๋ผ๊ณ ํ๋ค (์ง๊ธ ํ MemberServiceIntegrationTest.java
์ฒ๋ผ)
๋ณดํต์ ์์ํ ๋จ์ํ ์คํธ๊ฐ ๋ ์ข์ ํ ์คํธ์ผ ํ๋ฅ ์ด ๋๋ค.
๊ฐ์ฅ ์์ ๋จ์๋ก ๋๋์ด ํ ์คํธ๋ฅผ ์งํ.
์คํ๋ง ์ปจํ ์ด๋ ์์ด ํ ์คํธ.
(ํ ์คํธ ํ๋ ๋ฐ์ ์คํ๋ง ์ปจํ ์ด๋๊น์ง ํ์ํด์ง๋ ์ํฉ ์์ฒด๊ฐ ์ข์ง ์์ ์ํฉ์ผ ํ๋ฅ ์ด ๋๋ค)
๋ฐ๋ผ์ ๋จ์ํ ์คํธ๋ฅผ ๋ ์ ๋ง๋๋ ์ฐ์ต์ ํ์.
์คํ๋ง JdbcTemplate
- ์์ JDBC์ ๋์ผํ ํ๊ฒฝ์ค์ ์ ๊ฐ๋๋ค.
- ์คํ๋งJdbcTemplate ๋ฐ MyBatis๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ JDBC API์์ ๋ณธ ๋ฐ๋ณต ์ฝ๋๋ฅผ ๋๋ถ๋ถ ์ ๊ฑฐํด์ค๋ค.
- ํ์ง๋ง SQL์ ์ง์ ์์ฑํด์ผ ํ๋ค.
- ์ค๋ฌด์์๋ ๋ง์ด ์ฌ์ฉํ๋ค.
์คํ๋งJdbcTemplate์ ์ด๋ฆ์ ๋์์ธํจํด์ ํ ํ๋ฆฟ๋ฉ์๋ํจํด์ ๋ง์ด ์ฌ์ฉํ ๋ฐ์ ์ ๋ํ๋ค.
1
JdbcTemplateMemberRepository.java
๋ผ๋ ์๋ก์ด ํ ์คํธํด๋์ค๋ฅผ ๋ง๋ค์ด ์งํํ๋ค.
(src/main/java/ํ๋ก์ ํธ/repository/JdbcTemplateMemberRepository.java
)
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
public class JdbcTemplateMemberRepository implements MemberRepository{
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcTemplateMemberRepository(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Member save(Member member) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new HashMap<>();
parameters.put("name", member.getName());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
member.setId(key.longValue());
return member;
}
@Override
public Optional<Member> findById(Long id) {
List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
return result.stream().findAny();
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return jdbcTemplate.query("select * from member", memberRowMapper());
}
private RowMapper<Member> memberRowMapper() {
return new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
Member member = new Member();
member.setId(rs.getLong("id"));
member.setName(rs.getString("name"));
return member;
}
};
}
}
JdbcTemplate
- import ํด์ ์ฌ์ฉ (
org.springframework.jdbc.core.JdbcTemplate
) - ์์ฑ์์์ JdbcTemplate ๋์
DataSource
๋ฅผ ์ธ์ ์ ๋ฐ๋๋ค. - JdbcTemplate๋ฅผ ์์ฑํ ๋๋
(dataSource)
๋ฅผ ์ธ์ ์ ๋ฐ๋๋ค. - ์์ ํํ๋ก ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๋ค.
์ฐธ๊ณ : ์์ฑ์๊ฐ ํ๋๋ง ์์ ๋๋
@Autowired
๋ฅผ ์๋ตํ ์ ์๋ค. (์คํ๋ง์ด ์๋์ผ๋ก ๋ฃ์ด ์ค๋ค) ๋ ๊ฐ ์ด์์ผ ๋๋ ์ ๋๋ค.
List<Member> result = jdbcTemplate.query(sql, ๊ฒฐ๊ณผ, ํ๋ผ๋ฏธํฐ)
query()
๋ DB์ sql๋ฌธ์ ๋ณด๋ด๋ ๋ฉ์๋์ด๋ค. ๋ฆฌํดํ์ ์ List์ด๋ค.ํ๋ผ๋ฏธํฐ
๋sql
๋ฌธ์?
์ ๋์ํ๋ ๊ฐ์ ๋ฃ๋๋ค.
SimpleJdbcInsert
JdbcTemplate๋ฅผ ๋ฃ์ด์ ๋ง๋๋ ๊ฐ์ฒด.
์ฟผ๋ฆฌ๋ฌธ์ ์ง์ ์์ฑํ์ง ์์๋ ๋๊ฒ ๋์์ค๋ค.
private RowMapper<Member> memberRowMapper()
- ๊ฐ์ฒด ์์ฑ์ ๋์์ฃผ๋ ๋งตํผ?
- ์ฌ๊ธฐ์๋
query()
์ ๊ฒฐ๊ณผ๋ฅผ Member๊ฐ์ฒด๋ก ๋ณํํ ๋ค์ ๋๋ ค์ฃผ๋ ์ฉ๋๋ก ์ฌ์ฉํ๋ค.
JdbcTemplate ์์ฒด๋ ์์ฒญ๋๊ฒ ๋ง์ ๊ธฐ๋ฅ๋ค์ด ์๊ธฐ ๋๋ฌธ์ ๊ฒ์ํด ์์๋ณด์. (๋งค๋ด์ผ)
2
JdbcTemplateMemberRepository.java
๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ SpringConfig.java
์์ ๋ค์ ๋ฐ๊ฟ ๋ผ์ ์ฌ์ฉํด ๋ณด์.
SpringConfig.java
์๋ ๋ฉ์๋๋ฅผ ์์ ํ๋ค.
// ...
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
return new JdbcTemplateMemberRepository(dataSource);
}
// ...
3
MemberServiceIntegrationTest.java
๋ก ์ ๋ฐ๊ฟ ๋ผ์์ก๋์ง ํ
์คํธ ํด ๋ณธ๋ค.
ํ ์คํธ ์ฝ๋์ ์ค์์ฑ
์ค๋ฌด์์๋ ๊ทผ๋ฌด์๊ฐ์ 60-70%๋ ํ ์คํธ์ฝ๋ ๊ฐ๋ฐ์, 30-40%๋ Production Code(์ค์ ์ฌ์ฉํ ์ฝ๋) ๊ฐ๋ฐ์ ํ๋ค.
์ค๋ฌด์์๋ ์์ ๋ฒ๊ทธ ํ๋๋ ์ ์ฒด ์์คํ ์ ์น๋ช ์ ์ด๊ธฐ ๋๋ฌธ์ (์์ญ์ต์์ ํผํด),
ํ ์คํธ์ฝ๋๋ฅผ ๊ผผ๊ผผํ๊ฒ ์ ์ง๋ ๊ฒ์ด ์์ฒญ๋๊ฒ ์ค์ํ๋ค.
JPA
JPA : Java Persistence API์ ์ฝ์.
JPA๋ '์๋ฐ์ ํ์ค ์ธํฐํ์ด์ค'์ด๊ณ , ๊ทธ ๊ตฌํ์ฒด๋ก ๋ณดํต hibernate๋ฅผ ์ฌ์ฉํ๋ค.
JPA์ ์ฅ์ ๋ฐ ์ฌ์ฉ ์ด์
- JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ๋ณธ์ ์ธ SQL๋ฌธ์ JAP๊ฐ ์ง์ ๋ง๋ค์ด ์คํํด ์ค๋ค.
- ์คํ๋ง JdbcTemplate์ ์ฌ์ฉํด ๋ฐ๋ณต ์ฝ๋๋ ์ค์์ง๋ง, ์ฌ์ ํ SQL๋ฌธ์ ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ฑํด์ผ ํ๋ค. JPA๋ฅผ ์ฌ์ฉํ๋ฉด SQL์ ์ง์ ์์ฑํ์ง ์์๋ ๋๋ค.
- ํนํ ๊ธฐ๋ณธ CRUD ๊ธฐ๋ฅ์ SQL๋ฌธ์ ์ง์ ์์ฑํ ํ์๊ฐ ์๋ค.
- JPA ์ฌ์ฉ์ ํตํด SQL ๋ฐ ๋ฐ์ดํฐ ์ค์ฌ ์ค๊ณ์์ ๊ฐ์ฒด ์ค์ฌ์ ์ค๊ณ๋ก ํจ๋ฌ๋ค์์ ์ ํํ ์ ์๋ค.
- JPA๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ๋ฐ ์์ฐ์ฑ์ ํฌ๊ฒ ๋์ผ ์ ์๋ค.
ํ์ฌ JPA์ MyBatis๋ผ๋ API๋ฅผ ๋น๊ตํ ์ ์๋๋ฐ, ์์ง๊น์ง ๊ตญ๋ด์์๋ MyBatis๋ฅผ ๋ ์ฐ์ง๋ง ์ ์ธ๊ณ์ ์ธ ์ถ์ธ๋ก๋ JPA(68%)๋ฅผ ํจ์ฌ ๋ ๋ง์ด ์ด๋ค. ๊ตญ๋ด ๋์ ์ด ๋ฆ์ด์ง ๊ฒ.
์คํ๋ง ๊ธฐ์ ๋ ๋งค์ฐ ๊ฑฐ๋ํ์ง๋ง JPA๋ ๊ทธ๋งํผ ๊ฑฐ๋ํ ๊ธฐ์ ์ด๋ค. ์คํ๋ง๋งํผ ๊ณต๋ถ๊ฐ ํ์ํ๋ค.
์คํ๋ง๊ณผ JPA๊ฐ ๋น์ทํ ์๊ธฐ์ ๋์์ ์คํ๋ง์ด JPA๋ฅผ ์ง์ํ๋ ๋ถ๋ถ์ด ๋ง๋ค.
1
build.gradle
์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
...
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
// implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
...
*org.springframework.boot:spring-boot-starter-data-jpa
๊ฐ Jdbc ๊ด๋ จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํฌํจํ๊ธฐ ๋๋ฌธ์ jdbc๋ ์ ๊ฑฐํด๋ ๋๋ค.
์์ ํ gradle์ ์๋ก๊ณ ์นจ ํ๋ค.
2
application.properties
๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
: JPA๊ฐ ์์ฑํ๋ sql์ ์ถ๋ ฅํ๋ค.spring.jpa.hibernate.ddl-auto=none
: JPA๋ ์๋ ๊ฐ์ฒด๋ฅผ ๋ณด๊ณ table(DB)์ ์๋์ผ๋ก ๋ง๋ค์ด ์ฃผ์ง๋ง, ์ฐ๋ฆฌ๋ H2์ ์ด๋ฏธ ๋ง๋ค์ด ๋์๊ณ ๋ง๋ค์ด์ง ๊ฒ์ ์ฌ์ฉํ ๊ฒ์ด๋ฏ๋ก ํด๋น ๊ธฐ๋ฅ์ ๋๋ค.none
๋์create
๋ฅผ ์ฌ์ฉํ๋ฉด ์ํฐํฐ ์ ๋ณด๋ฅผ ๋ฐํ์ผ๋ก ํ ์ด๋ธ์ ์ง์ ์์ฑํด ์ค๋ค.
3
JPA๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ ์ํฐํฐ Entity ๋ผ๋ ๊ฒ์ ๋งตํํด์ผ ํ๋ค.
JPA๋ ORM : Object Relational Mapping ๊ธฐ์ ์ ์ฌ์ฉํ๋ค.
- ๊ฐ์ฒด(Object)์ ๊ด๊ณํ(Relational) ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ์ ๋งตํ(Mapping)ํ๋ ๊ธฐ์ .
JPA์ ๋งตํ ๊ณผ์ ์ ์ด๋ ธํ ์ด์ ์ ํตํด ์ด๋ฃจ์ด์ง๋ค.
์ฆ ์๋ฐ ์ฝ๋์ ์ ์ธํ ์ด๋ ธํ ์ด์ ๋ค์ ๋ณด๊ณ , ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์์์ ๋น๊ตํ๋ค.
Member.java
package hello.hellospring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity
javax.persistence.Entity
๋ฅผ import@Entity
๋ฅผ ์ ์ธํ๋ฉด ํด๋น ํด๋์ค ๊ฐ์ฒด๋ 'JPA๊ฐ ๊ด๋ฆฌํ๋ ์ํฐํฐ'๋ผ๊ณ ํํํ๋ค.
๊ทธ๋ฆฌ๊ณ PK (Primary Key)๋ฅผ ๋งตํํด ์ฃผ์ด์ผ ํ๋ค. ์ฐ๋ฆฌ ์์ ์์๋ ID์ด๋ค.
@Id
import javax.persistence.Id
ID๊ฐ ์์ ์ ์ธํด ์ค๋ค.
@GeneratedValue
SQL์์ ์ ์ธํ
generated by default as identity
์ฒ๋ผ, DB๊ฐ ์๋์ผ๋ก ์์ฑํด์ฃผ๋ ๊ฐ์ ์ ์ธํ๋ค.์ด๋ฅผ Identity ์์ด๋ดํฐํฐ์ด๋ผ๊ณ ํ๋ค.
ID๊ฐ์ด ์๋ ์์ฑ ๊ฐ์ด๋ฏ๋ก
@Id
์ ๋๋ํ ์ ์ธํด ์ค๋ค.
JpaMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
public class JpaMemberRepository implements MemberRepository{
private final EntityManager em;
public JpaMemberRepository(EntityManager em) {
this.em = em;
}
@Override
public Member save(Member member) {
em.persist(member);
return member;
}
@Override
public Optional<Member> findById(Long id) {
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
@Override
public Optional<Member> findByName(String name) {
List<Member> result = em.createQuery("select m from Member m where m.name = :name", Member.class)
.setParameter("name", name)
.getResultList();
return result.stream().findAny();
}
@Override
public List<Member> findAll() {
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
}
EntityManager
- JPA๋ EntityManager๋ก ๋์ํ๋ค. ๋ฐ๋์ ์ ์ธํด์ผ ํ๋ ์์์ด๋ค.
build.gradle
์์data-jpa
๋ฅผ ์ค์ ํด ๋๋ฉด, ์คํ๋ง ๋ถํธ๊ฐ ์๋์ผ๋ก EntityManager๋ผ๋ ๊ฒ์ ๋ง๋ค์ด ์ค์ ํ DB์์ ํต์ ์ ํธํ๊ฒ ํด ์ค๋ค.- ์์ฑ์์์ ์ด ๋ง๋ค์ด์ง EntityManager๋ฅผ ์ฃผ์ ๋ฐ๋ ๊ฒ์ด๋ค.
persist(e)
- ์ ์ฅ. DB์ ํด๋น ์์๋ฅผ ์์์ ์ผ๋ก ์ ์ฅํจ.
find(์กฐํํ ํ์ , PK)
- ์กฐํ. DB ํ ์ด๋ธ์์ ํด๋น ํ์ ์ PK๋ฅผ ๊ฐ๋ ์์๋ฅผ ์ฐพ์ ์ด.
createQuery("JPQL์ฟผ๋ฆฌ๋ฌธ", ํ์ )
- JPQL์ฟผ๋ฆฌ๋ฌธ์ ์์ฑํ๋ ๋ฉ์๋?
- findByName(), findAll()์ฒ๋ผ PK๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์กฐํํ ๋ ํด๋น ๋ฉ์๋๋ฅผ ์ฌ์ฉํด JPQL์ ์์ฑํด์ผ ํ๋ค.
JPQL
์ผ๋ฐ ์ฟผ๋ฆฌ๋ฌธ์ ํ ์ด๋ธ์ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฌธ์ ๋ณด๋ด์ง๋ง,
JPQL์ ๊ฐ์ฒด(์ ํํ๋ ์ํฐํฐ)๋ฅผ ๋์์ผ๋ก ์ฟผ๋ฆฌ๋ฌธ์ ๋ณด๋ธ๋ค. JPQL๋ก ๋ณด๋ธ ์ฟผ๋ฆฌ๋ฌธ์ด SQL๋ก ๋ฒ์ญ๋์ด ๋ณด๋ด์ง๋ค.
select m from Member m
: Member(m
) ์์ฒด๋ฅผ ์กฐํํ๊ณ ์๋ค.
๋ค์ ์๊ฐ์ ๋ฐฐ์ธ ''์คํ๋ง ๋ฐ์ดํฐ JPA''๋ฅผ ์ฌ์ฉํ๋ฉด JPQL๋ ์ง์ ์ง์ง ์์๋ ๋๋ค.
4
โ JPA๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์์
: ๋ฐ์ดํฐ ์ ์ฅ ๋ฐ ๋ณ๊ฒฝํ ๋๋ ๋ฐ๋์ ํด๋น ํด๋์ค/๋ฉ์๋์ @Transsactional
์ด๋
ธํ
์ด์
์ ์ ์ธํด์ผ ํ๋ค.
JPA๋ ๋ชจ๋ ๋ฐ์ดํฐ ์ ์ฅ/๋ณ๊ฒฝ์ด Transaction ์์์ ์คํ๋์ด์ผ ํ๋ค.
MemberService.java
- ํด๋์ค ์ ์ธ๋ถ ์์
@Transactional
์ ์ถ๊ฐํ๋ค.
5
์ด์ ์์ฑํ JpaMemberRepository.java
๋ฅผ ์ฌ์ฉํ๋๋ก SpringConfig.java
๋ฅผ ์์ ํ๋ค.
SpringConfig.java
package hello.hellospring;
import hello.hellospring.repository.JpaMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;
@Configuration
public class SpringConfig {
private EntityManager em;
public SpringConfig(EntityManager em) {
this.em = em;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
// return new MemoryMemberRepository();
// return new JdbcMemberRepository(dataSource);
// return new JdbcTemplateMemberRepository(dataSource);
return new JpaMemberRepository(em);
}
}
private EntityManager em
๋ง์ฐฌ๊ฐ์ง๋ก ์ ์ธ ํ ์์ฑ์์์ ์ฃผ์ ํด ์ค๋ค
JpaMemberRepository(EntityManager em)
ํ๋ผ๋ฏธํฐ๋ก ์ฌ์ฉํ๋ค
6
์ฐ๊ฒฐํ์ผ๋ MemberServiceIntegrationTest.java
์์ ํ
์คํธ๋ฅผ ํด ๋ณธ๋ค.
ํ ์คํธ ๊ฒฐ๊ณผ ๋ฉ์์ง ๋ก๊ทธ๋ฅผ ์์ธํ ๋ณด๋ฉด, ์คํ๋ง ์ ์ ํ ์๋์ ๊ฐ์ ๋ฉ์์ง๊ฐ ์๋ค.
Hibernate: select member0_.id as id1_0_, member0_.name as name2_0_ from member member0_ where member0_.name=?
Hibernate: insert into member (id, name) values (null, ?)
- JPA๋ ์ธํฐํ์ด์ค์ด๊ณ , ๊ตฌํ์ฒด๋ก Hibernate๋ฅผ ์ฌ์ฉํ๋ค.
- ์ฆ ๊ฒฐ๊ตญ DB์ ์ ๋ฌ๋๋ ๊ฒ์ SQL๋ฌธ์ธ๋ฐ, ์ง์ ์์ฑํ ํ์๊ฐ ์๊ฒ ํธ๋ฆฌ์ฑ์ ๋ํ ๊ฒ.
+
์์ฆ JPA๋ ํฐ ์คํํธ์ ๋ฑ์์๋ ์ฌ์ฉํ๊ณ ํด์ธ์์๋ ์ํ์์๋ ์ธ ๋งํผ ๋ง์ด ์ฌ์ฉ๋๋ค.
JPA๋ ์คํ๋ง๋งํผ ๊ณต๋ถํ ๊ฒ ๋ง๋ค. ์ค๋ฌด์์ ์ฌ์ฉํ ์ ๋๊ฐ ๋๋ ค๋ฉด ๊น์ด ์๊ฒ ๊ณต๋ถํ ํ์๊ฐ ์๋ค.
์คํ๋ง ๋ฐ์ดํฐ JPA
์คํ๋ง ๋ถํธ์ JPA๋ง ์ฌ์ฉํด๋ ๊ฐ๋ฐ ์์ฐ์ฑ์ด ๋งค์ฐ ๋์์ง๊ณ , ๊ฐ๋ฐํด์ผ ํ๋ ์ฝ๋๋ ํฌ๊ฒ ์ค์ด๋ ๋ค.
์ฌ๊ธฐ์ ์คํ๋ง ๋ฐ์ดํฐ JPA๊น์ง ์ฌ์ฉํ๋ฉด ์ธํฐํ์ด์ค๋ง์ผ๋ก ๊ฐ๋ฐ์ ์๋ฃํ ์ ์๋ค.
ํนํ, ๊ธฐ๋ณธ CRUD๊ธฐ๋ฅ์ ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์ ๊ณตํ๋ค!
์คํ๋ง ๋ถํธ & JPA ๋ผ๋ ๊ธฐ๋ฐ ์์, ์คํ๋ง ๋ฐ์ดํฐ JPA๋ผ๋ ํ๋ ์์ํฌ๋ฅผ ๋ํ๋ฉด ๊ฐ๋ฐ์ด ์ฆ๊ฑฐ์์ง๋ค.
๋จ์ ๋ฐ๋ณต ์ฝ๋๋ฅผ ์ค์ฌ ์ฃผ์ด, ๊ฐ๋ฐ์๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง ๊ฐ๋ฐ์๋ง ์ง์คํ ์ ์๋ค.
์ค๋ฌด์์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฌ์ฉํ๋ค๋ฉด, ์คํ๋ง ๋ฐ์ดํฐ JPA๋ ํ์์ด๋ค.
โ
์คํ๋ง ๋ฐ์ดํฐ JPA๋ JPA๋ฅผ ํธ๋ฆฌํ๊ฒ ์ฐ๋๋ก ๋์์ฃผ๋ ๋๊ตฌ์ผ ๋ฟ์ด๋ค.
๋ฐ๋ผ์ ๋ฐ๋์! JPA๋ฅผ ๋จผ์ ์ฐ์ตํ ๋ค์์ ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ํ์ตํด์ผ ํ๋ค.
JPA๋ฅผ ๋ชจ๋ฅด๋ ์ํ์์ ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ฐ๋ ๊ฒ์ ์ํํ ํ์์ด๋ค. ์ค์ ์ด์์ ํ๋ฉฐ ๋ถ๋ชํ๋ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ ์ ์๊ฒ ๋๋ค.
1
ํ๊ฒฝ ์ค์ ์ JPA์ ์ค์ ์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค.
src/main/java/ํ๋ก์ ํธ/repository
์ "์ธํฐํ์ด์ค"๋ฅผ ๋ง๋ ๋ค.
(์ง๊ธ๊น์ง์ ๋ฌ๋ฆฌ '์ธํฐํ์ด์ค'๋ฅผ ๋ง๋ ๋ค๋ ๊ฒ์ ์ ์ํ๋ค)
SpringDataJpaMemberRepository.java
package hello.hellospring.repository;
import hello.hellospring.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository {
@Override
Optional<Member> findByName(String name);
}
public interface SpringDataJpaMemberRepository extends JpaRepository<Member, Long>, MemberRepository
- ์ธํฐํ์ด์ค๊ฐ ์ธํฐํ์ด์ค๋ฅผ ๋ฐ์ ๋๋
implements
๊ฐ ์๋extends
๋ฅผ ์ฌ์ฉํ๋ค!JpaRepository
์MemberRepository
๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ๋ฐ์ ์ค๋ฏ๋กextends
๋ฅผ ์ฌ์ฉ.- โ ์ธํฐํ์ด์ค๋ ๋ค์ค์์์ด ๊ฐ๋ฅํ๋ค!!
JpaRepository<Member, Long>
<ํค, ๊ฐ>
์ธ๋ฐ, ํค๋ Member, ๊ฐ์ Entity์ ์๋ณ์์ธ PK(id
)์ ํ์ ์ธ Long์ ๋ฃ๋๋ค.
2
โ ์ค์ ์๋ฆฌ
: JpaRepository
๋ฅผ ์์๋ฐ์ ์ธํฐํ์ด์ค๋, ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ๊ทธ ๊ตฌํ์ฒด๋ฅผ ์๋์ผ๋ก ๋ง๋ค๊ณ ์คํ๋ง ๋น์ ๋ฑ๋กํด ์ค๋ค. ์ฆ, ๋ด๊ฐ SpringDataJpaMemberRepository
๋ฅผ ๊ตฌํํ๊ณ ์คํ๋ง ๋น์ ๋ฑ๋กํ ํ์๊ฐ ์๋ค. ์ฐ๋ฆฌ๋ ๊ทธ ๊ตฌํ์ฒด๋ฅผ ๊ฐ์ ธ๋ค ์ฐ๋ฉด ๋๋ค. ์๋์ ๊ฐ๋ค.
SpringConfig.java
package hello.hellospring;
import hello.hellospring.repository.JpaMemberRepository;
import hello.hellospring.repository.MemberRepository;
import hello.hellospring.service.MemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SpringConfig {
private final MemberRepository memberRepository;
@Autowired
public SpringConfig(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Bean
public MemberService memberService() {
return new MemberService(memberRepository);
}
// @Bean
// public MemberRepository memberRepository() {
// // return new MemoryMemberRepository();
// // return new JdbcMemberRepository(dataSource);
// // return new JdbcTemplateMemberRepository(dataSource);
// // return new JpaMemberRepository(em);
// }
}
โ ์์ฑ์์ ์ฃผ์
๋๋ MemberRepository
๋ ๋ฌด์์ธ๊ฐ?
โ ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์๋์ผ๋ก ๋ง๋ค์ด ๋ ์ธํฐํ์ด์ค SpringDataJpaMemberRepository
์ ๊ตฌํ์ฒด์ด๋ค.
๊ฒ๋ค๊ฐ MemberRepository๋ฅผ ๋ค์ค์์๋ฐ์๊ธฐ ๋๋ฌธ์ MemberRepository
๋ก ์ฃผ์
๋ ์ ์๋ ๊ฒ์ด๋ค.
3
๋ง์ฐฌ๊ฐ์ง๋ก MemberServiceIntegrationTest.java
๋ก ์ ๋์ํ๋์ง ํ
์คํธ ํด ๋ณธ๋ค.
์คํ๋ง ๋ฐ์ดํฐ JPA์ ์ ๊ณต ๊ธฐ๋ฅ
- ์ธํฐํ์ด์ค๋ฅผ ํตํ ๊ธฐ๋ณธ์ ์ธ CRUD
- ์ฐ๋ฆฌ๊ฐ ์๊ฐํ ์ ์๋ ๋๋ถ๋ถ์ ์์ฑ, ์กฐํ, ์์ , ์ญ์ ๋ฉ์๋๋ค์ ์ ๊ณตํ๋ค.
- findByName() , findByEmail() ์ฒ๋ผ ๋ฉ์๋ ์ด๋ฆ ๋ง์ผ๋ก ์กฐํ ๊ธฐ๋ฅ ์ ๊ณต
- ํ์ด์ง ๊ธฐ๋ฅ ์๋ ์ ๊ณต
๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก ์กฐํ ๊ธฐ๋ฅ?
๊ฐ ๋น์ฆ๋์ค๋ง๋ค ๋ฌ๋ผ์ง๋ ๋ด์ฉ์ ๊ณตํต์ ์ด ์๊ธฐ ๋๋ฌธ์, ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ๋ฉ์๋๋ก ์ ๊ณตํ ์ ์๋ค.
์ด๋ฐ ๋ถ๋ถ์ SpringDataJpaMemberRepository
์ธํฐํ์ด์ค์ฒ๋ผ ์ง์ ๋ง๋ค์ด ์ฃผ์ด์ผ ํ๋ค.
(์ธํฐํ์ด์ค์ ๋ฉ์๋ ์ด๋ฆ๋ง์ผ๋ก ์ฟผ๋ฆฌ๋ฌธ ๊ฐ๋ฐ์ ๋๋ผ ์ ์๋ค)
์๋ฅผ ๋ค์ด , ์๊น ๋ง๋ค์๋ ์ธํฐํ์ด์ค์ ์๋ ๋ถ๋ถ์ ํ์ธํ์๋ฉด,
@Override
Optional<Member> findByName(String name);
๊ท์น : findBy***()
ํ์์ ๋ฉ์๋๋ ์คํ๋ง ๋ฐ์ดํฐ JPA๊ฐ ์๋์ ๊ฐ์ด ์ฟผ๋ฆฌ๋ฅผ ์์ฑํด ์ค๋ค.
select m from Member m where m.*** = ?
์ฐ๋ฆฌ๋
findByName()
์ด๋ฏ๋กselect m from Member m where m.name = ?
์ด๋ผ๋ ์ฟผ๋ฆฌ๊ฐ ์์ฑ๋ ๊ฒ์ด๋ค.
์์ฉ : findByNameAndId(String name, Long id)
์ด๋ฐ ์์ผ๋ก๋ ๊ฐ๋ฅํ๋ค.
+
์ฐธ๊ณ : ์ค๋ฌด์์๋ JPA์ ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ๊ธฐ๋ณธ์ผ๋ก ์ฌ์ฉํ๊ณ , ๋ณต์กํ ๋์ ์ฟผ๋ฆฌ๋ Querydsl์ด๋ผ๋
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. Querydsl์ ์ฌ์ฉํ๋ฉด ์ฟผ๋ฆฌ๋ ์๋ฐ ์ฝ๋๋ก ์์ ํ๊ฒ ์์ฑํ ์ ์๊ณ , ๋์
์ฟผ๋ฆฌ๋ ํธ๋ฆฌํ๊ฒ ์์ฑํ ์ ์๋ค. ์ด ์กฐํฉ์ผ๋ก ํด๊ฒฐํ๊ธฐ ์ด๋ ค์ด ์ฟผ๋ฆฌ๋ JPA๊ฐ ์ ๊ณตํ๋ ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ฅผ
์ฌ์ฉํ๊ฑฐ๋, ์์ ํ์ตํ ์คํ๋ง JdbcTemplate๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค.