TIL - 0815

스프링부트 환경설정 알아보고 dev/prod 환경에 따른 설정하기

외부에서 어플리케이션 환경설정하기

  • 주로 사용되는 방법은 application.properties 파일에 설정을 함
    • key-value 형태로 정의해두면 어플리케이션 내에서 사용할 수 있음 : 설정값 가져오는 방법(1) - @Value로
  • 다양한 외부에서 환경설정 방법이 있어서 적용 우선순위가 있음
    • 스프링부트가 기본적으로 어플리케이션 구동 시 필요한 설정을 이미 해둔 것이 있음 : 우선순위가 가장 낮음 - 기본 설정된 key-value 살펴보기
    • jar 실행 시 커맨드라인으로도 환경설정을 할 수 있음 : properties 파일로 선언하는 것보다 적용 우선순위가 높음(훠어어얼씬 높음)
    • 테스트 환경에서만 적용되도록 하는 방법이 또 있음 : 특정 환경의 설정 파일인 경우 적용 우선순위가 더 높음
  • jar 실행 시 커맨드라인으로 값 설정하기
java -jar $jar_path_and_name --$property_key=$property_value

test/prod 용도 설정 파일 나눠보기

  • 테스트 용도로만 사용하는 설정이 있을 때는? test 디렉토리 아래에 src와 같이 application.properties를 만들면 됨
    • 적용 우선순위가 더 높기때문에 비교 체크할 때 test 디렉토리 하위에 위치한 설정값으로 비교해야 통과함
    • 주의할 점은 같은 이름(application.properties)으로 만들었을 때 기존 파일과 설정사항이 같지않으면 에러포인트가 될 수 있음(테스트 빌드할 때 같은 resources 위치에 같은 이름으로 있으면 덮어써버림 - resources의 경우 컴파일 결과 하나의 디렉토리에 위치함)
    • 설정값 가져오는 방법(2) : Environment 객체를 주입받아서 key를 넘기면 설정된 value가 리턴됨
    • Environment 객체 역할 : 현재 실행 중인 어플리케이션의 환경에 대한 설정, 값을 가지는 객체
    • 파일 덮어쓰기 되지않도록 같은 이름이 아니라 특정 이름으로 만든 뒤 @TestPropertySource 설정하면 됨 : 특정 파일을 읽어오도록
    • 다른 이름이 아니라 같은 이름 다른 위치에 놓을 수도 있음 : 관련 spring-boot docs, 우선순위가 있으므로 우선순위 높은 것으로 덮어쓰기(파일 덮어쓰기는 아님 - 같은 디렉토리 같은 이름이 아니기때문에)
/* /src/resources/application.properties */
author.name=colin

/* /test/resources/application.properties */
author.name=tester

@RunWith(SpringRunner.class)
@SpringBootTest
public class ConfigTest {

    @Autowired
    Environment environment;
    
    @Test
    public void configTest() {
        assertThat(environment.getProperty("author.name"), is("tester"));
    }
}

@RunWith(SpringRunner.class)
@TestPropertySource(location = "classpath:/test.properties")
@SpringBootTest
public class ConfigTest {

}

설정을 빈으로 관리할 수 있다

  • properties에 있는 key-value를 바인딩 해서 빈 생성 후 관리 : key(필드명), value(필드의 값)
    • 단, 디펜던시를 추가해야함 : configuration-processor
    • 자바빈 규약을 따라야함 : 규약만 잘 따르면 get할 때 안전함
    • Environment로 full name(key) get을 하거나 @Value를 사용해서 설정을 가져오지않고 빈을 주입받아 get하면 됨
    • 자바 config라서 가능한 검증 : @Validated(체킹 on 설정) 설정해두고 각 프로퍼티마다 검증 어노테이션(체킹 내용 설정)을 달아서 설정값에 대한 검증을 할 수 있음
/* build.gradle */
compile('org.springframework.boot:spring-boot-configuration-processor')


/* config bean */
@Component
@ConfigurationProperties("author")
@Validated
public class ApplicationProperties {

    @NotEmpty
    private String name;
    
    @Min(1)
    private int age;
    
    private String fullName;

    public String getName() {
        return name;
    }

    public ApplicationProperties setName(String name) {
        this.name = name;
        return this;
    }

    public int getAge() {
        return age;
    }

    public ApplicationProperties setAge(int age) {
        this.age = age;
        return this;
    }

    public String getFullName() {
        return fullName;
    }

    public ApplicationProperties setFullName(String fullName) {
        this.fullName = fullName;
        return this;
    }
}


/* application.properties */
author.name=colin
author.age=27
author.fullname={author.name} Park


/* test code */
@Autowired
ApplicationProperties properties;

@Test
public void configBeanTest() {
    assertThat(properties.getAge(), is(27));
}

@Profile 어노테이션 사용하기

  • 설정 파일을 여러개 만들어두고 모두 사용하지않고, 환경에 따라 특정 설정 파일만 읽도록 할 때 사용하는 어노테이션
  • @Profile을 설정할 때 이름을 설정함
    • 설정파일에서 @Profile의 이름만 변경하면 @Profile로 설정된 @Configuration이 on/off 됨 : 해당 이름의 파일만 설정으로 읽어들임
  • 스프링부트에서 설정파일 여러개 만든 후 특정 설정만 읽어들이도록 해보기
@Profile("prod")
@Configuration
public class ProductionConfiguration {
    
    @Bean
    public String getGreet() {
        return "hello";
    }
}

@Profile("test")
@Configuration
public class TestConfiguration {
    
    @Bean
    public String getGreet() {
        return "bye";
    }
}


/* application.properties */
spring.profiles.active=test


// 출력 결과
==========
bye
==========
  • application.properties에서 기본설정 해두고 더 우선순위 높은 커맨드라인에서 스프링부트 어플리케이션 실행시킬 때 값을 덮어쓸 수도 있음
    • 실행 환경에 따라 값만 변경하면 같은 프로퍼티지만 다른 값을 가지도록 할 수 있다는 것임
$ java -jar $jar_path_and_name --spring.properties.active=prod

// 출력 결과
==========
hello
==========

특정 환경에 맞는 설정 파일 만들기

  • application.properties(기본)보다 만든 프로퍼티 파일이 우선순위가 높기때문에 선언되어있는 값이 덮어쓰기가 됨
    • 네임 컨벤션 : application-xxx.properties
    • .properties에는 기본적으로 spring.properties.include 설정이 제공됨 : 값으로 해당 설정파일과 같이 로드될 파일을 지정할 수 있음
  • 실행 환경에 따라 실행 시 인자로 active할 프로퍼티명만 변경해주면 됨(혹은 include를 사용해도 되고)
    • 빈으로 관리해야한다면 위의 방법 참고해서 설정하기

배포, 개발 환경에 따라 데이터베이스(RDBMS) 설정 다르게하기

  • 환경에 따른 버젼 정보 : 개발 환경에서는 빠른 개발, 테스트를 위해 인메모리 데이터베이스인 H2를 사용함, 배포 환경에서는 MySQL을 사용함
/* 배포환경(prod) */
* OS : AWS Ubuntu 16.04
* MySQL 5.7.23(JPA/Hibernate)


/* 개발환경(dev) */
* H2 Database(JPA/Hibernate)


/* dependency(gradle) */
compile('mysql:mysql-connector-java:5.1.38')
  • 설정 파일
    • prod, dev 각각 나눠서 만들고 application.properties에 기본적인 값(dev)을 설정해두고 배포환경에서 실행 시 설정 인자로 prod를 줘서 값 덮어쓰기가 되도록 함
    • 테스트 했을 때 mysql 접속 및 테이블 생성 제대로 되는 것을 확인함, dev로 전환했을 때에도 h2 정상 동작
/* application.properties */
spring.profiles.include=file


/* application-dev.properties */
spring.datasource.url=jdbc:h2:~/java-ims;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.h2.console.enabled=true


/* application-prod.properties */
spring.datasource.url=jdbc:mysql://localhost:3306/ims?useSSL=false
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=create
spring.datasource.username=
spring.datasource.password=
  • 배포환경에서 pull하기 전 개발환경에서 mysql 설정 부분 잘 동작하는지 체크하기
    • 개발환경에서도 마찬가지로 mysql 설치
    • IDE에서 실행 Config 만들어서 prod로 변경 후 실행되도록 설정 : 커맨드로 해도 되지만 귀찮으니깐 IDE가 제공해주는 편리함을 사용하기

  • 당연히 알아야하는 것
    • mysql을 비롯한 배포환경 서버에서 사용하는 데이터베이스의 포트를 외부에서 접근가능하도록 열어두면 안됨 : 외부 데이터베이스 서버를 사용하면 모를까 로컬에 위치한 데이터베이스를 사용하면서 외부 접근 가능하도록 열어두면 보안상 위험(3306을 닫자)
  • 설정하면서 발생한 문제(라고 쓰고 아직 모르기때문에 금방 해결하지못한 것으로 읽기)
    • SSL 인증 문제 : username, password로 인증만으로 안되던데(mysql 일정 버젼부터는 인증이 기본 설정되어있다는데…?), 우선 꺼두기로 했음
    • DDL auto 설정 : DDL이란 데이터베이스 테이블 생성 등 정의와 관련한 SQL을 말함, 해당 쿼리문을 언제 발생시킬지에 대해 JPA 설정해주는 것으로 대충 알고 있는데 확실하게 구분하고 설정하기위해서는 공부해야겠음(각 설정값이 의미하는 바가 무엇인지)
    • prod 설정 파일을 github에 공유하지않고, prod에서 하드코딩해서 가지고 있도록 해야할지 생각해봐야함 : mysql 접근 id, passwd가 그대로 적혀있으니 문제가 됨 - 우선적으로 prod 파일은 올리되 아이디 비밀번호는 직접 타이핑하도록 하기로 했음

Spring-data-JPA

ORM과 JPA

  • 객체지향 패러다임 프로그램과 관계형 데이터베이스를 맵핑해주는 프레임워크
    • 객체지향 : 객체 간의 협업에 의해 프로그램의 기능을 만든다는 패러다임, 객체를 다룸
    • 관계형 데이터베이스 : 2차원 표로 데이터를 관리하는 프로그램, 키를 가지고 데이터를 다룸
    • 비슷한 듯 다른 두 프로그램의 패러다임 차이를 중간에서 없애주는 역할이 ORM
  • JPA는 자바 진영의 ORM 표준 명세
    • ORM을 만들기위해서는 해당 명세를 지켜야함
    • 대표적인 구현체가 Hibernate
    • 베이스로 JDBC를 추상화하고 있음 : JDBC는 자바에서 데이터베이스로 커넥트하기위한 표준 명세(동일한 방식으로 각기 다른 데이터베이스에 접근 가능함), RDBMS를 다룰 때 SQL을 작성하고 보내는 작업에 대해 추상화함
  • JDBC 코딩(with docker - postgres image)과 단점
    • 객체에 맞게 혹은 테이블에 맞게 쿼리 생성 혹은 클래스 추상화를 해야함
    • 커넥션 만들고 자원 해제 하는 것 자체에 대한 비용이 많이 듦 : 커넥션 관리
    • 코드 자체도 더러워요 : 반복적인 코드 발생, try ~ catch 혹은 try resource catch
    • SQL 표준 이외 방언을 써야할 때 수동으로 변경 혹은 테이블에 변경이 생겼을 때 모든 SQL을 다 수정해줘야한다는 점
public class Application {

    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/springdata";
        String username = "jinbro";
        String password = "pass";

        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            System.out.println("Connections created : " + connection);

            String sql = "CREATE TABLE ACCOUNT (id int, username varchar(255), password varchar(255), primary key (id))";
            try (PreparedStatement statement = connection.prepareStatement(sql)) {
                statement.execute();
            }
        } catch (SQLException e) {
            System.out.println(e.getMessage());
        }
    }
}
  • JPA 사용 : 쿼리 생성 및 자원(Connection)관리, 사용에 대해 개발자는 관여하지않음(추상화됨)
    • 쿼리를 직접 작성해야할 때(튜닝 등)는 JPA가 제공하는 방법을 이용해서 쿼리 생성 및 전송을 할 수도 있음