。 영속성 DB 와 연결하는것이므로 H2와 같은 In-Memory DB처럼 Application을 재시작하면 Data가 초기화되는것이 아니기 때문에 초기값 설정용 data.sql , schema.sql을 정의 안해도된다.
▶ DB 사용 시 해당 .sql파일을 만들 필요는 없다.
。Spring의 PSA에 의해 MySQL, H2 DB 등에 Spring JPA를 통해 구현한 DB Entity의 구현내용을 재활용하여 PostgreSQL DB에도 적용이 가능하다.
▶ Spring JDBC , Spring JPA를 사용할 경우 PostgreSQL DB , MySQL DB , H2-DB 등에 관계없이 구축된 DB Entity을 매우 쉽게 적용할 수 있다.
▶ Spring JDBC의 JdbcTemplate Class를 이용하여 SQL을 하드코딩하여 PostgreSQL DB와 상호작용을 수행할 수 있다.
- Spring Boot에
PostgreSQL DB연결 시 필요한 dependency
Maven
。pom.xml에 정의。
Spring Web<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>。
Spring JDBC<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jdbc</artifactId> </dependency>。
Spring Data JPA<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>。
PostgreSQL Driver<dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>
Gradle
。build.gradle에 정의implementation 'org.springframework.boot:spring-boot-starter-web' // Spring Web implementation 'org.springframework.boot:spring-boot-starter-jdbc' // Spring JDBC implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // Spring Data JPA implementation group: 'org.postgresql' , name: 'postgresql' , version: '42.2.23' // PostgreSQL Driver runtimeOnly 'org.postgresql:postgresql'
application.yml에PostgreSQL DBDataSource정의
。Spring Boot가 기본적으로 자동으로 연결할PostgreSQL DB의DataSource를 설정하기위해application.yml에postgresql에 관한 정보를 정의.
▶@Bean을 선언하여 직접DataSourceinstance를 생성하는 방법은 아래에서 소개.
▶JdbcUserDetailsManager(DataSource객체)에서 사용됨.
。Spring Boot가 자동으로 연결할DataSource를 설정 시application.properties에 기존에 정의된h2-db설정을 삭제해야한다.spring: datasource: url: jdbc:postgresql://localhost:5432/All4Runner username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:wjd747} jpa: database-platform: org.hibernate.dialect.PostgreSQLDialect show-sql: true hibernate: ddl-auto: update
spring.datasource.url=JDBC URL:
。 특정 DB의JDBC URL
▶ Local에서 실행되고 있는PostgreSQL DB의 URL을 정의.
。H2-DB에서 동적 URL을 고정하기 위한 정적 JDBC URL 설정 시에도 사용됨.
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username,spring.datasource.password:
。DB에 접근하기 위한 사용자 이름과 비밀번호
spring.jpa.database-platform:
。Hibernate가 사용할PostgreSQL DB의 Dialect
spring.jpa.show-sql:
。SQL Query를 Console에 출력할지의 여부
spring.jpa.hibernate.ddl-auto:
。Application 실행 시Hibernate가 DB Schema를 자동 Update할지의 여부를 설정.
▶spring.jpa.hibernate.ddl-auto=update로 설정할 경우,EntityClass와DB Table Schema를 동기화.
Hibernate
。JPA를 구현한 대표적인 오픈소스.
。Hibernate JAR을 class path에 추가해서 Hibernate를 JPA 구현체로 사용.
Dialect:
。JPA또는Hibernate에서 사용하는 특정 DB에 해당하는 SQL문법과 기능을 정의한 설정.
▶ 각 DB(MySQL,PostgreSQL,Oracle)등은 공통적으로 SQL 표준을 따르지만, 각각 조금씩 차이가 발생하는 SQL 문법을 가지므로Hibernate는 해당 문법/기능적 차이를Dialect를 통해 자동으로 조정.주요
Dialect
org.hibernate.dialect.MySQLDialect:MySQLorg.hibernate.dialect.MariaDBDialect:MariaDBorg.hibernate.dialect.PostgreSQLDialect:PostgresDBorg.hibernate.dialect.H2Dialect:H2DB
Spring JDBC의PostgreSQL DBDataSourceinstance 생성하기
。PostgreSQL에서JdbcUserDetailsManager(DataSource객체)을 통한 DB에 사용자 자격증명을 추가할때의 용도로 활용할DataSourceinstance 정의하기. JdbcUserDetailsManager 설정
▶ 사용자 자격증명 저장이 필요없는 경우, 설정안해도application.properties에서Spring Boot에 자동연결설정된DataSource를 활용하여 기본적으로CRUD를 수행 가능.
DataSource생성하는@Bean Method정의
。@Configuration이 선언된Configuration Class에서DataSourceinstance를 반환하는@Bean Method를 생성.@Bean public DataSource pgdataSource() { HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:postgresql://localhost:5432/GeoDB"); config.setUsername("postgres"); config.setPassword("wjd747"); return new HikariDataSource(config); }。
postgresql DataSource를 정의하기위해application.properties에서 작성된JDBC내용과 유사.
DataSource:javax.sql.DataSource
。JDBC에서 특정DB의DB Connection을 관리하는 Interface.
▶Application이DB에 접근 시DataSource를 통해 Connection을 관리.
。최적화된DB Connection Pool을 제공하고,Spring Boot에서Auto-Configuration이 가능.
HikariCP:
。Spring Boot에서 기본적으로 활용하는 고성능JDBC Connection Pool을 관리하는DataSourceInterface를 구현한 Class.
▶new HikariConfig()를 통해DB Connection이 구현된DataSourceinstance 생성.
。JDBC Connection Pool을 통해DriverManagerDataSource와 달리DB Connection을 재사용.
Connection Pool:
。DB Connection을 매번 생성 시 성능 저하 발생하는 단점을 보완.
▶Connection Pool을 사용 시 일정 수의DB Connection을 사전에 생성 및 재사용 가능하여 불필요한DB Connection생성을 방지 가능.
DriverManagerDataSource:
。Spring에서JDBC Connection을 위한DataSourceInterface를 구현한 Class.
▶new DriverManagerDataSource()를 통해DB Connection이 구현된DataSourceinstance 생성.
。 매번 새로운DB Connection을 생성하며 연결을 재사용하지 않고 사용하므로HikariCP에 대체됨.
Spring JPA기능을 활용하여PostgreSQL과 연동
。Spring JPA를 활용해REST API를 통해PostgreSQL DB와 상호작용 수행 시 활용.
。PostgreSQL DB의 Table과 Mapping된JPAEntityClass 생성
。H2 DB등Spring JPA를 통해 구현한DB Entity의 구현내용을 재활용하여PostgreSQL DB에도 적용이 가능하다.
- DB Entity 생성
。PostgreSQL DB와 Data Binding되어 상호작용을 수행하는EntityClass 생성
。@Entity를 선언하여EntityClass 생성 및@Id와@GeneratedValue를primary key field에 선언
▶ 기존H2-DB로 실습한EntityClass를 그대로 활용.// PostgresUser.java import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.persistence.*; import jakarta.validation.constraints.Past; import jakarta.validation.constraints.Size; import java.time.LocalDate; import java.util.List; @Entity public class PostgresUser { @Id @GeneratedValue Integer id; @JsonProperty("username") @Size(min=2, message = "Name should have at least 2 characters.") String name; @JsonProperty("birth_date") @Past(message="Birth Date should be in the past.") LocalDate birthDate; public List<PostgresPost> getPosts() { return posts; } public void setPosts(List<PostgresPost> posts) { this.posts = posts; } @OneToMany(mappedBy="user") @JsonIgnore private List<PostgresPost> posts; public PostgresUser(){} public PostgresUser(Integer id, String name, LocalDate birthDate) { this.id = id; this.name = name; this.birthDate = birthDate; } public Integer getId() { return id; } public String getName() { return name; } public LocalDate getBirthDate() { return birthDate; } public void setId(Integer id) { this.id = id; } public void setName(String name) { this.name = name; } public void setBirthDate(LocalDate birthDate) { this.birthDate = birthDate; } }。
idfield의 경우Wrapper Class의Integertype으로 설정.
▶int와 달리Wrapper Class객체 이므로Null값을 저장할 수 있다.
▶DB에서pk가 없거나 아직 할당되지 않은 상태를 지시하기 위해NULL을 사용할 수 있게 설정.
Integer vs int
int와Integer차이
。Java에서 둘다 정수를 다루지만, 기본 자료형(primitive type)과wrapper class라는 차이가 존재.
int
。기본자료형 (primitive type)
。null값을 가질 수 없다.
Integer
。java.lang.IntegerClass 객체
▶int를 객체로 감싼Wrapper Class객체
▶new키워드로 선언하여 객체 생성.
。null값을 저장 가능.
▶DB Entity의primary key로 활용.
JPA의CRUD를 수행하기위한JpaRepository<Entity,ID Field>Interface 정의import org.springframework.data.jpa.repository.JpaRepository; public interface PgRepository extends JpaRepository<PostgresUser,Integer> { }.
JPA의Business Logic을 구현하기위한Service Class정의
。Business Logic구현을 위한 Spring Bean이므로@Service선언.
▶@Entity에서 명시적 구체화.
。JpaRepository<>interface의 instance 생성 및 생성자주입을 수행하여CRUDmethod를 구현import java.util.List; import java.util.Optional; import org.springframework.stereotype.Service; @Service public class pgService { // JpaRepository instance PgRepository pgRepository; // @Autowired 생략 가능한 Constructor Based Dependency Injection public pgService(PgRepository pgRepository) { this.pgRepository = pgRepository; } // Create public PostgresUser CreatePGUser(PostgresUser user) { return pgRepository.save(user); } // Update public PostgresUser UpdatePGUser(PostgresUser user) { return pgRepository.save(user); } // Read public Optional<PostgresUser> GetPGUser(Integer id) { return pgRepository.findById(id); } public List<PostgresUser> GetAllPGUsers() { return pgRepository.findAll(); } // Delete public void DeletePGUser(Integer id) { pgRepository.deleteById(id); } }。
JpaRepository.save(DB Entity)는CREATE와UPDATE기능을 수행.
▶ 기존Entity를 사용 시UPDATE, 새로운Entity를 사용 시INSERT
CRUD기능을 포함한REST API를 구현할 Controller Class 생성
。JPA CRUD를 사전에 정의한pgServiceSpring Bean instance를 생성하여 의존성 주입하여 수행.import jakarta.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import java.net.URI; import java.util.List; import java.util.Optional; @RestController public class PgController { // JPA Business Logic을 사전에 정의한 Spring Bean pgService pgservice; // @Autowired 생략 가능한 Constructor Based Dependency Injection public PgController(pgService pgservice) { this.pgservice = pgservice; } // GET Method 구현 // GET /users : 모든 사용자 조회 @GetMapping(path="/pg/jpa/users") public List<PostgresUser> ListAllUsers(){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.findAll() Method 활용. List<PostgresUser> users = pgservice.GetAllPGUsers(); return users; } // GET /users/{id} : 특정 사용자 조회 @GetMapping(path="/pg/jpa/users/{id}") public Optional<PostgresUser> GetUserById(@PathVariable int id){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.findById() Method 활용. // 가져온 데이터는 Optional<PostgresUser>로 return. Optional<PostgresUser> user = pgservice.GetPGUser(id); if(user.isEmpty()){ // 해당 id가 존재하지 않는 경우 발생. throw new UserNotFoundException("id :" + id); } return user; } // POST Method 구현 // POST /users : 새로운 사용자 생성 @PostMapping(path="/pg/jpa/users") public ResponseEntity<PostgresUser> createUser(@Valid @RequestBody PostgresUser user) { // @RequestBody : HttpRequest의 Body(=JSON Format)를 Bean으로 변환하여 저장. // JpaRepository를 구현한 Service Class의 JpaRepository객체.save() Method 활용. PostgresUser savedUser = pgservice.SavePGUser(user); //REST API에서 알맞은 Response Status를 Client에게 반환하는 코드 URI locationHeader = ServletUriComponentsBuilder.fromCurrentRequest() .path("/{id}").buildAndExpand(savedUser.getId()).toUri(); // 201 : created status에 해당하는 ResponseEntity<User> 객체를 생성하여 반환. return ResponseEntity.created(locationHeader).build(); } // Post의 Update를 수행하고 Save한 PostgresPost instance를 return. // PUT /users/{id} @PutMapping(path="/pg/jpa/users/{id}") public PostgresUser UpdateUserById(@PathVariable int id, @RequestBody PostgresUser user){ // HTTP Request Body가 @RequestBody를 통해 변수에 Mapping되어 Update를 수행. pgservice.UpdatePGUser(user); return user; } @DeleteMapping(path="/pg/jpa/users/{id}") public void DeleteUserById(@PathVariable int id){ // JpaRepository를 구현한 Service Class의 JpaRepository객체.deleteById() Method 활용. pgservice.DeletePGUser(id); } }
。PostgreSQL DB에 해당EntityClass의 Table 명이 없을 경우Spring JPA에 의해 자동생성됨.
Spring JDBC기능을 활용하여PostgreSQL과 연동 Spring JDBC
。SQL을 조작하여PostgreSQL DB과 상호작용시 사용.
Spring JDBC를 통해 data의 삽입을 용이하게 하는 Spring Bean Class 생성
。해당 Class를 통해Spring Beaninstance를 생성하여Spring JDBC를 통해 DB에 Insert를 수행.import java.time.LocalDate; public class PostgresUserforJDBC { int id; String name; LocalDate birth_date; public PostgresUserforJDBC(int id, String name, LocalDate birth_date) { this.id = id; this.name = name; this.birth_date = birth_date; } public int getId() {return id; } public void setId(int id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} public LocalDate getBirth_date() {return birth_date;} public void setBirth_date(LocalDate birth_date) {this.birth_date = birth_date; } }
Spring JDBC을 활용하여DAO를 구현하는 Class 생성
。Spring JDBC를 통해 하드코딩한 SQL를 DB에 반영하는 Class객체에@Repository를 선언하여 Spring Bean으로 구체화.
。특정 SQL문을JdbcTemplateClass의 instance를 활용하여JdbcTemplate객체.update(SQL구문)로 DB에 SQL을 반영하는 Logic을 구현.
。DAO(Data Access Obejct : DB에 접근하는 역할을 수행하는 객체)import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class pgJdbcRepository { private JdbcTemplate jdbcTemplate; // @Autowired : 생성자기반 의존성주입 public pgJdbcRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // text-block을 활용한 sql 문자열 private static String sqlinsert = """ insert into postgres_user(id,birth_date,name) values(?,?,?); """; public void insert(PostgresUserforJDBC user){ jdbcTemplate.update(sqlinsert,user.getId(),user.getBirth_date(),user.getName()); } }
CommandLineRunner상속 Class 생성
。CommandLineRunner를 상속한 Class를 정의하여run()을 통해 application 구동 시 DB와 상호작용하는 기능을 구현한Spring Bean( =pgJdbcRepositor) instance의 method를 작동.
▶JdbcTemplate의update(sql)method로 DB와 상호작용을 수행하는 method를 구현.
。Query문입력 시text-block내에 SQL문을 입력.import org.springframework.boot.CommandLineRunner; import org.springframework.stereotype.Component; import java.time.LocalDate; @Component public class pgCodeLineRunnerClass implements CommandLineRunner { private pgJdbcRepository jdbcRepository; // @Autowired : 생성자기반 의존성주입 public pgCodeLineRunnerClass(pgJdbcRepository jdbcRepository) { this.jdbcRepository = jdbcRepository; } @Override public void run(String... args) throws Exception { jdbcRepository.insert(new PostgresUserforJDBC(2, "Lee",LocalDate.now())); } }。
@SpringBootApplicationClass의Component Scan에 의해@Component가 선언된 해당CommandLineRunnerClass를 식별하여run() method를 실행하여DAO에서 구현된JdbcTemplate와SQL로Spring JDBC기능을 실행.
。다음처럼 성공적으로insert into postgres_user(id,birth_date,name) values(?,?,?);SQL 구문을 통해 Data Insert가 완료된것을 확인 가능.
JDBC/Spring JDBC/JPA/Spring Data JPA차이
JDBC:
。SQL을 많이 작성하면서 Java 코드가 많다.
▶ 하드코딩이 많음.
Spring JDBC:
。SQL을 많이 작성
。JdbcTemplateinstance를 통해 Java코드는 적게 사용.
JPA:
。SQL을 작성하지않음
。EntityManager을 통해DB Entity를 DB Table로 Mapping 하는 과정 필요.
Spring JPA:
。SQL,EntityManager을 사용하지 않는다.
▶EntityManager대신JpaRepository<Entity class, ID>Interface를 상속한 Interface를 Instance로 생성하여 활용.
。Spring JDBC,Spring JPA를 사용할 경우PostgreSQL DB,MySQL DB,H2-DB등에 관계없이 구축된DB Entity을 매우 쉽게 적용할 수 있다.
。Spring JDBC의JdbcTemplate객체의 경우 직접SQL을 작성하여JdbcTemplate객체.update(SQL)로 상호작용한다면JpaRepository는 자동으로Query가 생성되는 Method를 제공.
DAO( Data Access Object ) :
。DB와 직접 상호작용을 수행하는Object로서 개발자가SQL을 직접 작성하여CRUD를 수행.
▶Spring에서는 보통@RepositoryAnnotation으로 선언하여Spring Beaninstance으로 생성.
。JdbcTemplate같은 Spring JDBC를 활용하여 DB와 상호작용하는 method를 제공하는 Class를 instance로 생성하여 활용.
ORM( Object-Relational Mapping )
。Application의DB Entity Classinstance를 RDBMS의DB Table에 자동으로 영속화 하는것을 의미.
▶DB Entityinstance와RDBMS DB Table간 자동 Mapping.
。SQL을 직접 사용하지 않고,DB Entityinstance를 중심으로DB의 Data 조작을 수행.
▶RDBMS(H2-DB,mySQL,postgreSQL) 의 변경에도 유연한 적용이 가능.