3.1 ORM 개념 이해하기
Persistence Framework란?
- persistance: 영속성
- 데이터의 영속성: 휴대폰을 새로 구매해도 기존에 있던 데이터를 끌어올 수 있는 것처럼 데이터가 없어지지 않는 특성을 말한다.
- Persistance Framework는 DB와의 연동되는 시스템을 빠르게 개발하고 안정적인 구동을 보장해주는 프레임워크를 말한다.
- 장점: 재사용, 유지 보수에 용이하다. 코드가 직관적이다.
- 종류
- SQL Mapper: SQL을 개발자가 직접 작성한다. 매핑: 쿼리 수행 결과 <=> 객체
- 단점: DB 종류 변경 시에 쿼리 수정해야한다. 비슷한 쿼리를 반복적으로 작성해야한다.
- ORM(Object Relation Mapping): Object와 DB 테이블을 매핑한다. Java 함수를 사용하면 자동으로 SQL이 만들어진다. 매핑: DB 테이블 <=> 객체
- 단점: 복잡한 쿼리를 자바 메서서만으로 해결하는 것이 불편하다.
- SQL Mapper: SQL을 개발자가 직접 작성한다. 매핑: 쿼리 수행 결과 <=> 객체
3.2 JPA vs JDBC
JPA(Java Persistence API) vs JDBC(Java Database Connectivity)
- SQL Mapper라는 방식의 Persistence Framework를 자바로 쓰면 => JDBC
- ORM 방식의 Persistence Framework를 자바로 쓰면 => JPA
JDBC(Java Database Connectivity)
- JDBC는 SQL Mapper 중 하나이다.
- JDBC는 자바에서 DB를 사용할 수 있는 최소한의 API라고 볼 수 있다.
- 쿼리는 개발자가 알아서 짜야한다.
JPA(Java Persistence API)
- JPA는 자바용 공식 ORM이다. ORM 기능을 쓰기 위한 인터페이스를 모아둔 것이다.
- JPA는 자바 객체와 DB의 테이블을 연결해주면 프로그램이 스스로 쿼리를 짠다.
3.3 MySQL 연동 작업
MySQL 다운로드 및 설치 (윈도우)
- https://dev.mysql.com/downloads/mysql/
- go to download 클릭
- 사진에서 아래의 다운로드 버튼 클릭한다.(로그인 하지 않아도 다운로드 가능)
- 맨 위의 개발자 모드 선택
3.4 JDBC방식으로 데이터 저장하기
- 먼저 MySQL에서 데이터베이스 project를 추가한다.
JDBC로 데이터 저장하기
- build.gradle 파일에 jdbc, mysql 관련된 라이브러리를 추가한다.
<hide/>
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'mysql:mysql-connector-java'
}
- application.properties 파일에 DB 접속 정보 저장하기 - sql에서 저장한 비밀번호를 입력한다.
<hide/>
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/project?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username = root
spring.datasource.password = "비밀번호"
- MySQL에 memo 테이블 만들기
- auto_increment: 아이디 값을 정하지 않은 경우, 자동으로 1씩 늘려서 ID를 지정한다.
<hide/>
create table memo(
id INT NOT NULL primary key auto_increment,
text varchar(50) NOT NULL
);
- Memo라는 클래스를 만든다.
- 데이터베이스와 연결 시키기 위해서 테이블과 같은 필드 두 개를 만든다.
<hide/>
package zerobase.weather.domain;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Memo {
private int id;
private String text;
}
- JdbcMemoRepository: 보통 데이터베이스와 연동할 클래스들을 'repository'라고 한다.
- JdbcTemplate를 이용해서 MySQL과 연동하자
- @Autowired를 붙이면 properties 파일에서 DataSource를 자동으로 가져온다.
- 저장(update), 조회(query)
- JDBC
<hide/>
package zerobase.weather.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import zerobase.weather.domain.Memo;
import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
@Repository
public class JdbcMemoRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcMemoRepository(DataSource dataSource) { // properties에 저장한 정보들이 담긴다.
jdbcTemplate = new JdbcTemplate(dataSource);
}
// 데이터베이스에 저장
public Memo save(Memo memo){
String sql = " INSERT INTO memo values(?, ?); "; // 데이터베이스에 메모 클래스를 넣어준다.
jdbcTemplate.update(sql, memo.getId(), memo.getText());
return memo;
}
// 모든 메모를 조회한다.
private List<Memo> findAll(){
String sql = " SELECT * FROM memo; ";
return jdbcTemplate.query(sql, memoRowMapper()); // sql을 입력해서 나온 모든 객체가 반환된다. 데이터를 memoRowMapper()로 반환한다.
}
private Optional<Memo> findById(int id){
String sql = " SELECT * " +
" FROM memo " +
" WHERE id = ?;";
return jdbcTemplate.query(sql, memoRowMapper(), id).stream().findFirst();
}
// 데이터베이스에서 조회
private RowMapper<Memo> memoRowMapper(){ // 데이터베이스에서 가져온 rs를 스프링 부트에 메모 클래스 형태로 반환한다.
return (rs, rowNum) -> new Memo( // rs: resultSet
rs.getInt("id"),
rs.getString("text")
);
}
}
- 새로운 테스트를 만든다.
- @Test
- @Transactional: 데이터베이스를 테스트할 때 자주 쓰인다. 데이터베이스 테스트할 때는 내용이 변경되면 안된다. 이를 막기 위한 어노테이션
- Java에서 jdbc를 사용할 때는 Connection 객체가 필요했던 것처럼, Spring에서는 Datasource를 필요로한다.
<hide/>
package zerobase.weather.repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import zerobase.weather.domain.Memo;
import javax.sql.DataSource;
import java.util.List;
import java.util.Optional;
@Repository
public class JdbcMemoRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public JdbcMemoRepository(DataSource dataSource) { // properties에 저장한 정보들이 담긴다.
jdbcTemplate = new JdbcTemplate(dataSource);
}
// 데이터베이스에 저장
public Memo save(Memo memo){
String sql = " INSERT INTO memo values(?, ?) ; "; // 데이터베이스에 메모 클래스를 넣어준다.
jdbcTemplate.update(sql, memo.getId(), memo.getText());
return memo;
}
// 모든 메모를 조회한다.
private List<Memo> findAll(){
String sql = " SELECT * FROM memo ;";
return jdbcTemplate.query(sql, memoRowMapper()); // sql을 입력해서 나온 모든 객체가 반환된다. 데이터를 memoRowMapper()로 반환한다.
}
private Optional<Memo> findById(int id){
String sql = " SELECT * " +
" FROM memo " +
" WHERE id = ? ; ";
return jdbcTemplate.query(sql, memoRowMapper(), id).stream().findFirst();
}
// 데이터베이스에서 조회
private RowMapper<Memo> memoRowMapper(){ // 데이터베이스에서 가져온 rs를 스프링 부트에 메모 클래스 형태로 반환한다.
return (rs, rowNum) -> new Memo( // rs: resultSet
rs.getInt("id"),
rs.getString("text")
);
}
}
============================ 오류 =============================
- 오류: project라는 데이터베이스가 없다고 나온다.
- 원인: MySQL에서 "project"라는 데이터베이스를 만들지 않았다. 만들고나서 memo 테이블까지 만드니까 테스트 성공
3.5 JPA 방식으로 데이터 저장하기
JPA와 ORM 개념 - 면접 단골 질문
- ORM: DB테이블과 Java 객체를 매핑한다. SQL 자동 생성 ex) JPA
- SQL Mapper: 단순히 DB와 Java를 연결한다. SQL을 명시해줘야한다. ex) JDBC
- Dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
- application.properties 추가
- spring.jpa.show-sql = true: 내부적으로 생성된 쿼리를 자동으로 콘솔에 출력할 것인가? (show-sql)
- mysql의 데이터베이스를 쓰겠다고 선언
spring.jpa.show-sql = true
spring.jpa.database = mysql
- memo클래스에 @Entity 붙인다.
- 클래스를 엔티티화 한다.
- JPA 사용할 때는 DB 테이블과 매핑할 클래스에 @Entity를 꼭 붙인다.
- primary key를 붙여줘야 한다. @Id => 오로지 식별을 위한 용도로 사용한다.
- Id가 없을 때는 자동으로 증가하게끔 설정 가능하다.
- GenerationType.IDENTITY: MySQL이 만들어준 key값이 있으면 그걸 가져온다.
- 테이블 하나에 여러 엔티티를 매핑할 수 있다. 이럴 때는 name을 명시한다.
- @Table(name="Memo")
- @Entity(name="Memo")
- JpaMemoRepository
- Jpa은 자바의 표준 ORM 명세이다. 따라서, 자바에서 ORM 개념을 활용할 때 사용할 메서드들은 JpaRepo에 모두 정의되어 있다. <Memo, Integer> 뒤의 Integer 자리는 앞서 @Id로 지정한 키의 타입을 의미한다.
- JPA를 활용하면 아래와 같이 코드가 아주 간결해진다.
<hide/>
package zerobase.weather.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import zerobase.weather.domain.Memo;
@Repository
public interface JpaMemoRepository extends JpaRepository<Memo, Integer> {
}
- JPA test
- 위에 test insertMemoTest()는 통과한다.
- 아래 findById()는 ?
<hide/>
package zerobase.weather;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import zerobase.weather.domain.Memo;
import zerobase.weather.repository.JpaMemoRepository;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
class JpaMemoRepositoryTest {
@Autowired
JpaMemoRepository jpaMemoRepository;
@Test
void insertMemoTest(){
//given
Memo newMemo = new Memo(10, "this is new Memo");
// when
jpaMemoRepository.save(newMemo);
// then
List<Memo> memoList = jpaMemoRepository.findAll();
assertTrue(memoList.size() > 0);
}
@Test
void findById(){
Memo newMemo = new Memo(11, "jpa");
jpaMemoRepository.save(newMemo);
Optional<Memo> result = jpaMemoRepository.findById(11);
assertEquals(result.get().getText(), "jpa");
}
}
- 아래와 같이 실패한다.
- newMemo라는 객체에 필드 id의 값에 11을 지정해줬지만 틀리다고 나온다. (NoSuchElementException)
- 왜냐하면 데이터베이스에서 key 값이 자동 생성(auto-increment)되었기 때문이다. mysql에서 만들어진 Id 값은 11이 아닌 다른 값일 수 있는 것이다.
- 따라서 아래와 같이 코드를 바꿔주면 테스트 성공한다.
<hide/>
@Test
void findById(){
Memo newMemo = new Memo(11, "jpa");
Memo memo = jpaMemoRepository.save(newMemo);
Optional<Memo> result = jpaMemoRepository.findById(memo.getId());
assertEquals(result.get().getText(), "jpa");
}
출처 - 제로베이스 백엔드 스쿨 (김조현 강사님)
'Spring Projcect > 날씨 일기 프로젝트' 카테고리의 다른 글
Chapter 06. Spring Transaction(스프링 트랜잭션) (0) | 2022.08.31 |
---|---|
Chapter 05. 날씨 데이터 CRUD (0) | 2022.08.31 |
Chapter 04. 날씨 데이터 저장하기 (0) | 2022.08.31 |
Chapter 02. 프로젝트 준비하기 (0) | 2022.08.30 |
Chapter 01. 프로젝트 진행 전 준비 사항 (2) | 2022.08.30 |