Spring - PostgreSQL 연결

이정수·2025년 11월 9일

Spring Boot

목록 보기
18/25

Postgres DB와 Spring Boot 연결하기 참고

。 영속성 DB 와 연결하는것이므로 H2와 같은 In-Memory DB처럼 Application을 재시작하면 Data가 초기화되는것이 아니기 때문에 초기값 설정용 data.sql , schema.sql을 정의 안해도된다.
DB 사용 시 해당 .sql파일을 만들 필요는 없다.

SpringPSA에 의해 MySQL, H2 DB 등에 Spring JPA를 통해 구현한 DB Entity의 구현내용을 재활용하여 PostgreSQL DB에도 적용이 가능하다.
Spring JDBC , Spring JPA를 사용할 경우 PostgreSQL DB , MySQL DB , H2-DB 등에 관계없이 구축된 DB Entity을 매우 쉽게 적용할 수 있다.
Spring JDBCJdbcTemplate 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.ymlPostgreSQL DB DataSource 정의
    Spring Boot가 기본적으로 자동으로 연결할 PostgreSQL DBDataSource를 설정하기위해 application.ymlpostgresql에 관한 정보를 정의.
    @Bean을 선언하여 직접 DataSource instance를 생성하는 방법은 아래에서 소개.
    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로 설정할 경우, Entity Class와 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 : MySQL
        • org.hibernate.dialect.MariaDBDialect : MariaDB
        • org.hibernate.dialect.PostgreSQLDialect : PostgresDB
        • org.hibernate.dialect.H2Dialect : H2DB


  • Spring JDBCPostgreSQL DB DataSource instance 생성하기
    PostgreSQL에서 JdbcUserDetailsManager(DataSource객체)을 통한 DB에 사용자 자격증명을 추가할때의 용도로 활용할 DataSource instance 정의하기. JdbcUserDetailsManager 설정
    ▶ 사용자 자격증명 저장이 필요없는 경우, 설정안해도 application.properties에서 Spring Boot에 자동연결설정된 DataSource를 활용하여 기본적으로 CRUD를 수행 가능.
    • DataSource 생성하는 @Bean Method 정의
      @Configuration이 선언된 Configuration Class에서 DataSource instance를 반환하는 @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에서 특정 DBDB Connection을 관리하는 Interface.
      ApplicationDB에 접근 시 DataSource를 통해 Connection을 관리.

      。최적화된 DB Connection Pool을 제공하고, Spring Boot에서 Auto-Configuration이 가능.

    • HikariCP :
      。Spring Boot에서 기본적으로 활용하는 고성능 JDBC Connection Pool을 관리하는 DataSource Interface를 구현한 Class.
      new HikariConfig()를 통해 DB Connection이 구현된 DataSource instance 생성.

      JDBC Connection Pool을 통해 DriverManagerDataSource와 달리 DB Connection을 재사용.
      • Connection Pool :
        DB Connection을 매번 생성 시 성능 저하 발생하는 단점을 보완.
        Connection Pool을 사용 시 일정 수의 DB Connection을 사전에 생성 및 재사용 가능하여 불필요한 DB Connection 생성을 방지 가능.

      • DriverManagerDataSource :
        Spring에서 JDBC Connection을 위한 DataSource Interface를 구현한 Class.
        new DriverManagerDataSource()를 통해 DB Connection이 구현된 DataSource instance 생성.

        。 매번 새로운 DB Connection을 생성하며 연결을 재사용하지 않고 사용하므로 HikariCP에 대체됨.


  • Spring JPA 기능을 활용하여 PostgreSQL과 연동
    Spring JPA를 활용해 REST API를 통해 PostgreSQL DB와 상호작용 수행 시 활용.
    PostgreSQL DB의 Table과 Mapping된 JPA Entity Class 생성
    H2 DBSpring JPA를 통해 구현한 DB Entity의 구현내용을 재활용하여 PostgreSQL DB에도 적용이 가능하다.
    • DB Entity 생성
      PostgreSQL DB와 Data Binding되어 상호작용을 수행하는 Entity Class 생성
      @Entity를 선언하여 Entity Class 생성 및 @Id@GeneratedValueprimary key field에 선언
      ▶ 기존 H2-DB로 실습한 Entity Class를 그대로 활용.
      // 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; }
    }

    id field의 경우 Wrapper ClassInteger type으로 설정.
    int와 달리 Wrapper Class객체 이므로 Null값을 저장할 수 있다.
    DB에서 pk가 없거나 아직 할당되지 않은 상태를 지시하기 위해 NULL을 사용할 수 있게 설정.
    Integer vs int

    intInteger 차이
    Java에서 둘다 정수를 다루지만, 기본 자료형(primitive type)과 wrapper class라는 차이가 존재.

    • int
      。기본자료형 ( primitive type )
      null값을 가질 수 없다.

    • Integer
      java.lang.Integer Class 객체
      int를 객체로 감싼 Wrapper Class 객체
      new 키워드로 선언하여 객체 생성.

      null값을 저장 가능.
      DB Entityprimary key로 활용.


    • JPACRUD를 수행하기위한 JpaRepository<Entity,ID Field> Interface 정의
    import org.springframework.data.jpa.repository.JpaRepository;
    public interface PgRepository extends JpaRepository<PostgresUser,Integer> { }

    .

    • JPABusiness Logic을 구현하기위한 Service Class 정의
      Business Logic 구현을 위한 Spring Bean이므로 @Service 선언.
      @Entity에서 명시적 구체화.

      JpaRepository<> interface의 instance 생성 및 생성자주입을 수행하여 CRUD method를 구현
    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)CREATEUPDATE 기능을 수행.
    ▶ 기존 Entity를 사용 시 UPDATE, 새로운 Entity를 사용 시 INSERT

    • CRUD기능을 포함한 REST API를 구현할 Controller Class 생성
      JPA CRUD를 사전에 정의한 pgService Spring 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에 해당 Entity Class의 Table 명이 없을 경우 Spring JPA에 의해 자동생성됨.



  • Spring JDBC 기능을 활용하여 PostgreSQL과 연동 Spring JDBC
    SQL을 조작하여 PostgreSQL DB과 상호작용시 사용.
    • Spring JDBC를 통해 data의 삽입을 용이하게 하는 Spring Bean Class 생성
      。해당 Class를 통해 Spring Bean instance를 생성하여 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문을 JdbcTemplate Class의 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를 작동.
      JdbcTemplateupdate(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()));
        }
    }

    @SpringBootApplication Class의 Component Scan에 의해 @Component가 선언된 해당 CommandLineRunner Class를 식별하여 run() method를 실행하여 DAO에서 구현된 JdbcTemplateSQLSpring 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을 많이 작성
      JdbcTemplate instance를 통해 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 JDBCJdbcTemplate객체의 경우 직접 SQL을 작성하여 JdbcTemplate객체.update(SQL)로 상호작용한다면 JpaRepository는 자동으로 Query가 생성되는 Method를 제공.
      • DAO ( Data Access Object ) :
        DB와 직접 상호작용을 수행하는 Object로서 개발자가 SQL을 직접 작성하여 CRUD를 수행.
        Spring에서는 보통 @Repository Annotation으로 선언하여 Spring Bean instance으로 생성.

        JdbcTemplate 같은 Spring JDBC를 활용하여 DB와 상호작용하는 method를 제공하는 Class를 instance로 생성하여 활용.

      • ORM ( Object-Relational Mapping )
        。Application의 DB Entity Class instance를 RDBMS의 DB Table에 자동으로 영속화 하는것을 의미.
        DB Entity instance와 RDBMS DB Table 간 자동 Mapping.

        SQL을 직접 사용하지 않고, DB Entity instance를 중심으로 DB의 Data 조작을 수행.
        RDBMS ( H2-DB, mySQL , postgreSQL ) 의 변경에도 유연한 적용이 가능.
profile
공부기록 블로그

0개의 댓글