Sad Puppy 3 '분류 전체보기' 카테고리의 글 목록 (5 Page) :: 개발자 아지트

[Java-based Configuration]

: Java 코드와 스프링이 제공하는 어노테이션을 통해 스프링 컨테이너를 정의하는 방법 학습

 

[Declaring a Bean]

:@Configuration  어노테이션이 달린 클래스와 @Bean 어노테이션이 달린 메서드를 통해 Java 코드에서 스프링 빈을 등록할 수 있다. 

 

빈으로 등록하고자 하는 함수의 리턴값에서 실제 구현체가 없으면 빈이 등록되지 않는다. 

 

 

 

[Bean Dependencies]

:@Configuration 어노테이션이 있는 클래스 내의 메서드는 같은 클래스 내의 다른 @Bean 메서드를 호출해 빈 간의 의존성을 정의할 수 있다. 

 

@Configuration
@ComponentScan(basePackages = "cholog.scan")
public class AppConfig {

    @Bean
    public AuthService authService() {
        // 실제 구현체가 없으면 빈이 등록되지 않는다.
        return new AuthService();
    }
    
    @Bean
    public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver(AuthService authService) {

        return new AuthenticationPrincipalArgumentResolver(authService);
    }

}

 


[Property]

: 이는 애플리케이션의 구성값을 key-value 쌍으로 저장한다. 데이터베이스 연결 정보나 API 키 값을 예를 들 수 있다. 

스프링의 Environment 인터페이스는 이와 같은 프로퍼티 소스들을 통합해 관리하고, 필요한 프로퍼티 값을 조회하는 기능을 제공한다. 

 

[Using @PropertySource and Environment]

: @PropertySource 어노테이션을 사용해 프로퍼티 파일을 로드하고, Environment를 사용해 프로퍼티 값을 읽어올 수 있다. 

 

 

// TODO: Java-based Configuration을 하기 위한 클래스로 지정하기
// TODO: ext-api.properties 파일을 활용하기 위한 설정 추가하기
@Configuration
@PropertySource("classpath:ext-api.properties")
public class PropertySourceConfig {

    private final Environment env;

    public PropertySourceConfig(Environment env) {
        this.env = env;
    }

    // TODO: ext-api.properties의 google.api.endpoint 값을 Environment를 사용해서 가져오기
    // TODO: 위 endpoint 값을 사용하여 GoogleMapsRestClient를 빈으로 등록하기
    @Bean
    public GoogleMapsRestClient googleMapsRestClient() {
        String endpoint = env.getProperty("google.api.endpoint");
        return new GoogleMapsRestClient(endpoint);
    }

    // TODO: ext-api.properties의 google.api.endpoint 값을 어노테이션을 사용해서 가져오기
    // TODO: 위 endpoint 값을 사용하여 GoogleMapsRestClient를 빈으로 등록하기
    public GoogleDriveRestClient googleDriveRestClient() {
        return new GoogleDriveRestClient("");
    }
}

 

이해가 잘 안된다..

 

[Using @PropertySource and @Value]

: @PropertySource를 사용해 로드한 프로퍼티 파일의 값을 @Value 어노테이션을 통해 주입할 수 있다. 

스프링에서 @Value 어노테이션을 사용하면 ext-api.properties 파일에 있는 값을 필드나 메서드 파라미터에 주입할 수 있다. 

 

 

package cholog.property.config;

import cholog.property.GoogleDriveRestClient;
import cholog.property.GoogleMapsRestClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

@Configuration
@PropertySource("classpath:ext-api.properties")
public class PropertySourceConfig {

    private final Environment env;

    public PropertySourceConfig(Environment env) {
        this.env = env;
    }

    @Bean
    public GoogleMapsRestClient googleMapsRestClient() {
        String endpoint = env.getProperty("google.api.endpoint");
        return new GoogleMapsRestClient(endpoint);
    }
    
    @Value("${google.api.endpoint}")
    private String googleApiEndpoint;

    @Bean
    public GoogleDriveRestClient googleDriveRestClient() {
        return new GoogleDriveRestClient(googleApiEndpoint);
    }
}

 

[Externalized Configuration (Spring Boot)]

: 프로퍼티 값을 설정하는 방법은 @PropertySource 외에도 다양하다. 특히 Spring Boot 를 사용한다면 application.properties(혹은 application.yaml) 파일을 사용해 프로퍼티 값을 편하게 설정할 수 있다. 

 

package cholog.property.config;

import cholog.property.JwtTokenKeyProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// TODO: Java-based Configuration을 하기 위한 클래스로 지정하기
@Configuration
public class AuthConfig {
    // TODO: application.properties의 security.jwt.token.secret-key 값을 활용하여 JwtTokenKeyProvider를 빈으로 등록하기

    @Value("${security.jwt.token.secret-key}")
    private String secretKey;

    @Bean
    public JwtTokenKeyProvider jwtTokenKeyProvider() {
        return new JwtTokenKeyProvider(secretKey);
    }
}

 


[Profile]

: 프로파일은 애플리케이션 설정의 논리적인 그룹이다. 예를 들어, 개발(development), 테스트(testing), 운영(production)과 같이 다른 환경에 적합한 설정을 분리할 수 있다.

프로파일을 사용하면 같은 애플리케이션이지만 환경에 따라 다른 구성을 적용할 수 있다.

스프링이 제공하는 Environment 인터페이스는 현재 활성화되어 있는 프로파일과 기본 프로파일을 관리한다. 

 

[@Profile]

: @Profile 어노테이션을 이용하여 특정 프로파일에 따라 빈을 등록할 수 있다. @Profile 어노테이션은 클래스 레벨, 메서드 레벨에 모두 적용 가능하다. 

 

@Configuration 클래스에 적용하는 경우

 

클래스 내에서 정의된 Bean들은 development profile일 때만 등록된다. 

 

@Bean 메서드에 적용하는 경우

 

profile에 따라 등록되는 Datasource Bean이 달라집니다.

 

@Configuration
public class ProfileConfig {

    @Bean("dataSource")
    @Profile("dev")
    public MessageRepository inMemoryMessageRepository() {
        return new InmemoryMessageRepository();
    }

    @Bean("dataSource")
    @Profile("prod")
    public MessageRepository jdbcMessageRepository() {
        return new JdbcMessageRepository();
    }

}

 

 

 

스프링 컨테이너를 정의하는 방법은 다양합니다.@Component, @Autowired 어노테이션을 사용하는 방법과 비교하여 Java 코드로 빈을 관리할 때의 장단점에 대해 생각해보고, 어떤 상황에서 어떤 방식을 택할지 고민해보세요.이 외에도 XML을 사용해 스프링 컨테이너를 정의할 수도 있습니다. XML을 사용하는 방법에 대해 알아보고, Java 코드와 XML을 사용하는 방법을 비교해보세요.Spring Boot는 프로퍼티 파일 컨벤션(application-{profile})을 사용해 활성 프로파일에 대한 프로퍼티 파일을 로드합니다. 예를 들어, 활성 프로파일이 prod 라면 application.properties, application-prod.properties 파일을 로드합니다. 이러한 특성을 사용해 어떤 값을 프로퍼티 파일에서 관리할지 생각해 보세요.

 

[Spring Bean]

: 스프링은 애플리케이션의 복잡성을 줄이고, 유지보수를 용이하게 하기 위해서 객체의 생성, 설정 및 생명주기를 관리하는 스프링 컨테이너를 제공한다. 해당 컨테이너가 관리하는 객체를 스프링 빈이라고하고, 이를 통해서 의존성 주입이나 객체 관리가 자동화된다. 

 

[Bean Registration]

: 스프링에서 빈을 등록하는 방법은 다양한다. (= 빈을 만드는 방법)

 

어노테이션을 사용해 클래스에 추가하는 방법이 있다. 

 

@Component

: 스프링아, 이 클래스를 빈으로 만들어서 관리해줘! 

 

@Component
public class SpringBean {
    public String hello() {
        return "Hello";
    }
}

 

이제 스프링이 SpringBean 클래스를 관리하게 된다. 

 

 

[Bean Autowiring]

: 스프링 컨테이너에 등록된 객체는 매번 새로 생성할 필요 없이 컨테이너에서 가져와서 사용할 수 있다. (빈 자동 연결)

@Autowired
private SpringBean springBean;

 


[Dependency Injection]

:스프링 컨테이너에 등록된 스프링 빈 간의 의존성을 관리하는 방법은 다양하다. 그 중 어노테이션을 이용한 방법으로 생성자, 세터, 필드에 해당 @Autowired 어노테이션을 추가하는 방법이 있다. 

 

*참고로

: InjectionBean은 혼자 움직일 수 없고, ConstructorInjection이라는 것 안에서 움직일 수 있다. 

 

[Constructor Injection]

:스프링 컨테이너에 등록된 스프링 빈 간의 의존성을 생성자를 통해 주입하는 방법이다. 

 

private InjectionBean injectionBean;

public ConstructorInjection(InjectionBean injectionBean) {
    this.injectionBean = injectionBean;
}

 

여기서 'ConstructorInjection을 만들 때, InjectionBean이라는 객체를 미리 넣어줌으로써 의존성을 주입한다. 

 

[Setter Injection]

:스프링 컨테이너에 등록된 스프링 빈 간의 의존성을 세터를 통해 주입하는 방법이다. 

 

private InjectionBean injectionBean;

    @Autowired
    public void setInjectionBean(InjectionBean injectionBean){
        this.injectionBean = injectionBean;
    }

 

[Field Injection]

: 스프링 컨테이너에 등록된 스프링 빈 간의 의존성을 필드를 통해 주입하는 방법이다. 

 

@Autowired
private InjectionBean injectionBean;

 


[Component Scan]

: 스프링에서 특정 패키지를 스캔해, 그 패키지 안에 있는 @Component, @Service, @Repository, @Controller 등의 어노테이션이 붙은 클래스를 == 컨테이너에 등록된 스프링 빈을 자동으로 찾아 등록하는 역할을 한다.

 

[@ComponentScan]

: @ComponentScan 어노테이션을 이용해 스캔할 패키지를 지정할 수 있다. 앞에서 해당 어노테이션을 사용하지 않고도 정상동작했던 이유는 @SpringBootApplication이 @ComponentScan을 포함하고 있기 때문이다. 


@ComponentScan 이 덕분에 @Component로 표시된 클래스들이 자동으로 스프링 빈으로 등록되었던 것이다. 

@ComponentScan 이 어노테이션이스프링 빈을 관리하고, 자동으로 설정해주기 때문에 @Autowired가 정상적으로 작동할 수있다. 

 

 

@Configuration
@ComponentScan(basePackages = "cholog.scan")
public class ContextConfiguration {
}

 ComponenetScanBean을 Bean으로 등록하기

 

상위 패키지를 스캔하도록 함으로써 

상위패키지 아래에 등록된 빈을 사용하도록 만듦

[JdbcTemplate]

: 스프링은 데이터베이스와의 연동을 쉽게 도와주는 여러 도구와 방식을 제공한다. 

JDBC는 Java Database Connectivity의 약어로, 자바에서 데이터베이스에 접속하도록 해주는 API이다. 

JdbcTemplate은 이 API를 잘 사용할 수 있도록 스프링에서 제공하는 템플릿 클래스이다. 

이는 스프링 JDBC의 핵심이고, 다른 고수준의 기능들도 결국 내부에서는 이를 활용한다. 

 

jdbcTemplate은 핵심 JDBC 작업 흐름에 기본적인 업무를 수행하고, 애플리케이션 코드는 SQL을 제공하고 결과를 추출하는 역할을 한다. 

 

사용 효과 

: 데이터베이스 연동 코드를 보다 간결하고 안정적으로 작성할 수 있다. 

 

JdbcTemplate 클래스의 제공 기능

:

1. SQL 쿼리 실행

2. statements 및 저장된 procedure all 업데이트

3. ResultSet 인스턴스를 반복하고 반환된 매개 변수 값 추출을 수행

4. JDBC 예외를 캡처하고, org.springframework.dao 패키지에 정의된 일반적이고 더 유용한 에외 계층으로 변환함

 


[Querying (SELECT)]

 

스프링에서 JdbcTemplate을 이용해 SELECT 쿼리를 실행할 수 있는 여러 방법을 제공한다. 

queryForObject, query, queryForList, queryForRowSet, queryForMap 등의 메서드를 통해 쿼리를 실행할 수 있다. 

 


[Querying for a Single Object]

 

JdbcTemplate의 queryForObject 메서드를 이용해 단일 객체를 조회할 수 있다. 

 

[Object with Count]

queryForObject의 첫 매개변수는 쿼리문이고, 두 번째 매개변수는 조회 결과를 매핑할 클래스 타입이다. 

 

public int count() {
        int rowCount = jdbcTemplate.queryForObject("select count(*) from customers", Integer.class);
        return rowCount;
    }

사용예시

 

queryForObject는 Spring Framework의 JdbcTemplate 클래스에서 제공하는 메서드로, SQL 쿼리를 실행하여 단일 결과 값을 반환할 때 사용된다. 이 메서드는 데이터베이스 쿼리를 수행하고, 쿼리 결과의 첫 번째 행을 특정 타입의 객체로 매핑하여 반환한다. 주로 다음과 같은 상황에서 사용된다. 

 

 

단일 값 반환: 쿼리 결과가 단일 값(예: COUNT, SUM 등)인 경우, 이를 적합한 자바 기본형 또는 객체로 반환한다.

String sql = "SELECT COUNT(*) FROM users WHERE active = ?"; int count = jdbcTemplate.queryForObject(sql, Integer.class, true);

 

 

 

객체 반환: 쿼리 결과를 자바 객체로 매핑하여 반환한다. 이 경우, RowMapper 또는 해당 클래스의 타입을 지정해야 한다. 여기서는 User라는 클래스의 객체를 반환한다. BeanPropertyRowMapper는 결과셋의 컬럼 이름과 User 클래스의 필드 이름을 자동으로 매핑해준다.

String sql = "SELECT id, name, email FROM users WHERE id = ?"; User user = jdbcTemplate.queryForObject(sql, new Object[]{1}, new BeanPropertyRowMapper<>(User.class));

 

효과

:

예외 처리: 쿼리 결과가 없거나 두 개 이상의 결과가 반환될 경우 EmptyResultDataAccessException 또는 IncorrectResultSizeDataAccessException이 발생한다. 

 

단순성과 편리함: 간단한 쿼리를 사용하여 단일 결과를 처리할 때 매우 유용하다. 

queryForObject는 복잡한 SQL 쿼리 결과를 간단하게 자바 객체로 매핑할 수 있게 해준다. 

 

[Object with Parameter]

 

queryForObject의 세 번째 매개변수를 이용해, 쿼리문에 바인딩할 파라미터를 전달할 수 있다. 

public String getLastName(Long id) {
        //TODO : 주어진 Id에 해당하는 customers의 lastName을 반환
        String lastName = jdbcTemplate.queryForObject("select last_name from customers where id = ?", String.class, id);

        return null;
    }

 

첫번째 인자는 쿼리문

두번째 인자는 SQL  쿼리문의 결과값의 반환값

세번째 인자는 쿼리문 속에 ?에 들어갈 값 

 

*반환 타입을 명시하는 이유는?

:queryForObject 메서드에서 반환 타입을 명시하는 이유는 SQL 쿼리의 결과를 자바 객체로 매핑하기 위해서이다. 데이터베이스에서 가져온 데이터를 자바 객체로 변환할 때, 어떤 타입으로 변환할지를 명확히 지정해줘야한다.

 

예를 들어, last_name이 VARCHAR 타입이라면 이를 자바의 String 타입으로 반환해야 한다. 이를 위해 String.class를 한다

 

반환 타입을 명시함으로써 컴파일 시점에 타입 체크를 할 수 있어, 타입 안전성을 보장할 수 있다.

반환 타입을 명시함으로써 코드를 읽는 사람이 쿼리 결과가 어떤 타입인지 명확하게 이해할 수 있다. 코드의 가독성을 높이고, 유지보수를 쉽게 만들어주는 효과를 받을 수 있다. 

 

 

[Object with RowMapper]

 

queryForObject의 두 번째 매개변수에 RowMapper을 전달해 조회 결과를 매핑할 수 있다. 

 

*RowMapper란?

 

:Spring Framework에서 제공하는 인터페이스로, 데이터베이스 쿼리의 결과인 ResultSet을 자바 객체로 매핑해준다.  

RowMapper를 통해 ResultSet의 각 행(row)을 원하는 자바 객체로 변환할 수 있다. 데이터베이스의 테이블에서 데이터를 조회한 후, 해당 데이터를 특정 클래스의 객체로 매핑할 수 있다.

 

 public Customer findCustomerById(Long id) {
        String sql = "select id, first_name, last_name from customers where id = ?";

        Customer customer = jdbcTemplate.queryForObject(
                sql,
                (resultSet, rowNum) -> {
                    Customer customer1 = new Customer(
                            resultSet.getLong("id"),
                            resultSet.getString("fist_name"),
                            resultSet.getString("last_name")
                    );
                    return customer1;
                }, id);

        return null;
    }

RowMapper의 기능을 직접 람다식으로 구현한 것

 

resultSet에서 데이터베이스 쿼리의 결과를 담고 있다.

rowNum은 결과셋에서 몇 번째 행인지를 나타내는 숫자이다. 해당 코드에서는 사용되지 않았다. (작성은 했으나, 쓰이지 않았음)

 

getLong(), getString()등을 통해 각 열의 값을 가져온다. 

 

RowMapper는 데이터베이스에서 가져온 결과(ResultSet)를 자바 객체로 변환하는 인터페이스이다. 

이를 통해, 데이터베이스의 한 행을 자바 객체로 매핑하는 방법을 정의할 수 있다. 

 

queryForObject함수의 두번째 인자 값이 RowMapper의 mapRow메서드를 람다식으로 구현한 것이다. 

 

*mapRow()란?

: ResultSet의 각 행을 읽어들이고, 그 데이터를 사용해 자바 객체를 생성한다. 

 

결과적으로, queryForObject의 두번째 인자 값이 객체로 표현함으로써 저런식의 반환 타입을 명시하는것이다. 


[Querying for a List]

 

[List with RowMapper]

: jdbcTemplate의 query 메서드를 이용해 여러 객체를 조회할 수 있다. 두 번째 매개변수에 RowMapper을 전달해 조회 결과를 매핑할 수 있다. 

public List<Customer> findAllCustomers() {
        String sql = "select id, first_name, last_name from customers";

        List<Customer> customers = jdbcTemplate.query(
                sql,
                (resultSet, rowNum) -> {
                    Customer customer = new Customer(
                        resultSet.getLong("id"),
                        resultSet.getString("first_name"),
                        resultSet.getString("last_name")
                    );
                    return customer;
                });

        return customers;
    }

 

queryForObject의 세 번째 매개변수를 이용해 쿼리문에 바인딩할 파라미터를 전달할 수 있다. RowMapper의 경우, 별도 선언해 사용할 수 있다. 

 

public List<Customer> findAllCustomers() {
        String sql = "select id, first_name, last_name from customers";

        List<Customer> customers = jdbcTemplate.query(
                sql,
                (resultSet, rowNum) -> {
                    Customer customer = new Customer(
                        resultSet.getLong("id"),
                        resultSet.getString("first_name"),
                        resultSet.getString("last_name")
                    );
                    return customer;
                });

        return customers;
    }

 

이렇게 쓰면 쿼리로 불러와서 만든 customer이 customers에 들어간다. 


(추가적으로 공부하기)


[Updating (INSERT, UPDATE, and DELETE)]

: jdbcTemplate을 이용해, INSERT, UPDATE, DELETE 쿼리를 실행할 수 있다. 

 

쿼리문을 통해 insert를 할 때는 update함수를 이용한다. 

 

[Update (INSERT)]

 

 public void insert(Customer customer) {
        String sql = "insert into customers (first_name, last_name) values (?, ?)";
        jdbcTemplate.update(sql, customer.getFirstName(), customer.getLastName());
    }

 

insert into customers (first_name, last_name) values (?, ?) 
 
위 구문에 대한 설명
: 해당 SQL 구문은 customers 데이터베이스 테이블에 새로운 데이터를 삽입하는 INSERT 문이다. 
데이터베이스의 first_name과 last_name이라는 두 개의 열에 값을 삽입하겠다는 구문이다. 
 
values (?, ?)에 삽입할 값을 지정한다. (?는 플레이스 홀더 라고 하고, 나중에 코드에서 구체적인 값으로 대체된다)
 
 

[Update (DELETE)]

 

    public int delete(Long id) {
        //todo: id에 해당하는 customer를 지우고, 해당 쿼리에 영향받는 row 수반환하기
        String sql = "delete into customers where id = ?";
        Integer affectedRow = jdbcTemplate.update(sql, Long.valueOf(id));

        return affectedRow;
    }

 

jdbcTemplate.update() 메서드는 해당 쿼리에 의해 영향을 받은 행의 수를 반환한다. 

 

Long.valueOf()란?

:  Java에서 타입 변환을 수행하는 메서드 중 하나로, 주로 원시 타입 long 또는 int 값을 객체 타입 Long으로 변환하는 데 사용된다. (형변환 및 성능 최적화에 도움됨)

 

Long.valueOf(id)를 사용하는 이유는

: jdbcTemplate.update 메서드는 SQL 쿼리의 파라미터로 객체 타입을 받기 때문에 id 값이 long 또는 int 타입일 경우, 이를 명시적으로 Long 타입으로 변환하기 위해이다. 

 

 

[KeyHolder]

: 이는 Spring Framework에서 제공하는 인터페이스로, JdbcTemplate을 통해 데이터베이스에 새로운 행을 삽입하고 하고 자동으로 생성된 primary key를 가져올 수 있다. 

 

사용방법

: update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) 메서드를 통해 사용 

 

public Long insertWithKeyHolder(Customer customer) {
        String sql = "insert into customers (first_name, last_name) values (?, ?)";
        KeyHolder keyHolder = new GeneratedKeyHolder();

        jdbcTemplate.update(connection -> {
            PreparedStatement ps = connection.prepareStatement(
                    sql,
                    new String[]{"id"});
            ps.setString(1, customer.getFirstName());
            ps.setString(2, customer.getLastName());
            return ps;
        }, keyHolder);

        Long id = keyHolder.getKey().longValue();

        return keyHolder.getKey().longValue();
    }

 

connection은 객체이고, jdbc api에서 제공하는 데이터베이스와 상호작용 하기 위한 클래스이다. 

이는 자바 애플리케이션과 데이터베이스 간의 연결을 관리하는 객체이다. 

sql 쿼리 실행을 위해 Statement나 PreparedStatement 객체를 생성한다. 

 

prepareStatement 객체는  jdbc api에서 제공하는 데이터베이스와 상호작용 하기 위한 클래스이며, SQL문을 미리 준비해두기 위해 사용함. ? 를 사용하여 바인딩 한다. 

 

connection.prepareStatement(sql, new String[] {"id"});

 

해당 구문의 첫번째 인자는 sql문, 두번째 인자는 반환값으로 받을 값의 타입 설정 및, 어떤 값을 받을지에 대한 설정을 하는 자리이다. 위 구문은 id열을 String 배열로 받겠다는 의미이다. 

 

 

ps.setString(1, customer.getFirstName()); 

 

해당 구문의 첫번째 인자는 sql문에 있는 ?의 위치를 의미한다. 만약 1이라면, 첫번째 ?를 의미한다. 

두번째 인자는 삽입할 값을 의미한다. 

 

ps가 리턴된 후, keyHolder에는 해당 ps를 처리하고 난 후 발생한 key값을 저장하게된다. 

 

 

 

 

'프레임워크 > Spring' 카테고리의 다른 글

Spring Core 2  (0) 2024.08.14
Spring Core 1  (0) 2024.08.14
Spring mvc 4 (MVC Configuration, View Controller, Interceptor, Argument Resolver)  (0) 2024.08.06
Spring mvc 3 (예외처리)  (0) 2024.08.05
Spring mvc 2 (CRUD API)  (0) 2024.08.05

https://www.acmicpc.net/problem/2504

문제 해결 방법

1. 괄호가 정상적인지 확인한다. 

2. 스택을 만들고, 괄호열의 값을 계산하기 위한 변수를 선언한다. (변수의 초깃값은 1)

3. 여는 괄호일 경우, 변수에  괄호의 종류에 따라 2혹은 3을 곱한다. 

4. 닫는 괄호가 나올경우, 그 이후 또다른 여는 괄호가 나올 때까지 닫는 괄호가 나올 수 있다.
(여는 괄호가 나오지 않는다면 결국 모든 괄호열의 순회를 완료하게 됨)

단, 괄호를 연만큼 닫는 괄호가 나오지만, 괄호가 닫히지 않은 경우는 계속 나머지 값들에 영향을 줘야만 한다. 

따라서, 괄호가 닫히는 경우 나머지 값들에 영향을 주지 않기 위해 괄호의 종류에 따라 2 혹은 3으로 나눈다. 

5. 3-4를 괄호열 순회를 마칠 때 까지 수행한다. 

6. 결과값을 출력한다. 

 

 

코드 구현

정답 코드 

s = input()
stack=[]
check = 0
for i in s:
    if i =='(':
        stack.append('(')
    elif i == '[':
        stack.append('[')
    elif i == ')':
        if stack:
            if stack[-1]=='(':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check = 1
            print(0)
            break
    elif i == ']':
        if stack:
            if stack[-1]=='[':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check =1
            print(0)
            break
if check == 0:
    if stack:
        print(0)
    else:
        checkingStack = []
        result=0
        tmpV=1
        for index, j in enumerate(s):
            if j=='(':
                tmpV*=2
                check =1
                checkingStack.append('(')
            elif j=='[':
                tmpV*=3
                check =1
                checkingStack.append('[')

            elif j==')':
                if check ==1: # 최초로 닫는괄호 
                    result+=tmpV
                    tmpV=tmpV//2
                    check=0
                    checkingStack.pop()
                else:
                    tmpV=tmpV//2
                    checkingStack.pop()

            elif j==']':
                if check ==1: # 최초로 닫는괄호 
                    result+=tmpV
                    tmpV=tmpV//3
                    check=0
                    checkingStack.pop()
                else:
                    tmpV=tmpV//3
                    checkingStack.pop()

            #print('checkingStack', checkingStack, ' result: ', result, 'index: ', index, ' tmpV: ', tmpV)
        print(result)

 

시간/공간 복잡도

해당 문제는 구현문제이기 때문에 시간/공간 복잡도를 따지지 않는다. 

 

최적화 및 개선

따로 하지 않음

 

어려웠던 점

문제를 풀다보면 결국 관건은 괄호를 닫을때 지금까지 계산한 값을 곱셈( (X) 혹은 [X] )을 할 것이냐 덧셈(결합)을 할 것이냐를 구분하는 것이 어렵다. 그런데 어떤식으로 하든 해결할 방법이 생각나지 않았다. 

(후위 계산인가 싶어서 별거 다해봄)

 

결과적으로는 분배법칙의 아이디어를 통해 해당 문제를 해결했다. 

 

( ( ) [ [ ] ] ) 해당 괄호의 경우, 분배법칙을 적용하면 ...

 

( ( ) ) ( [ [ ] ] )  이렇게 풀 수 있다. 

 

두 괄호는 계산해보면 결과 값이 같다. 

 

이를 잘 고려하여, 계산을 위한 변수 값을 잘 다루어 문제를 해결할 수 있었다. 

 

 

흔적 (보지마세요)

더보기
# 괄호열이 뭔지? 

# 올바른 괄호열인지 체크 = 쌍을 이루는지 체크

# 닫기를 시작했을때, 계산시작 
# () = 2, [] = 3

# 다 닫고(if stack:) 새로 열때 +

s = input()
stack=[]
check = 0
for i in s:
    if i =='(':
        stack.append('(')
    elif i == '[':
        stack.append('[')
    elif i == ')':
        if stack:
            if stack[-1]=='(':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check = 1
            print(0)
            break
    elif i == ']':
        if stack:
            if stack[-1]=='[':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check =1
            print(0)
            break
if check == 0:
    if stack:
        print(0)
    else:
        print('좋다  시작하자 ')
        checkingStack = []
        cal=''
        result = 0
        for index, j in enumerate(s):
            if j=='(':
                if not checkingStack:
                    cal+='(2*'
                    checkingStack.append(j)
                else:
                    if checkingStack[-1]=='(':
                        cal+='((2+'
                        checkingStack.append(j)
                    elif checkingStack[-1]=='[':
                        cal+='(2#'
                        checkingStack.append(j)
                    elif checkingStack[-1]==')' or checkingStack[-1]==']':
                        cal+='+(2$'
                        checkingStack.append(j)
            elif j=='[':
                if not checkingStack:
                    cal+='(3'
                    checkingStack.append(j)
                else:
                    if checkingStack[-1]=='[':
                        cal+='*3'
                        checkingStack.append(j)
                    elif checkingStack[-1]=='(':
                        cal+='(3'
                        checkingStack.append(j)
                    elif checkingStack[-1]==']' or checkingStack[-1]==')':
                        cal+='+(3'
                        checkingStack.append(j)

            elif j==')':
                if checkingStack[-1]=='(':
                    cal+=')'
                    result= result+(result*2)
                    checkingStack.pop()
                

            elif j==']':
                if checkingStack[-1]=='[':
                    cal+=')'
                    result*=3
                    checkingStack.pop()
                    
            print('')
            print('j: ', j)
            print('checkingStack', checkingStack, ' result: ', result)
            print('cal',cal)
        check = 0

        # # + 추가 작업 // replace, find 다시 공부하기 
        # while 1:
        #     check = 0

        #     result1 = s.find(")(")
        #     result2 = s.find("][")
        #     result3 = s.find(")[")
        #     result4 = s.find("](")

        #     if result1!=-1:
        #         s=s.replace(")(", ")+(")
        #         check =1
        #     elif result2!=-1:
        #         s=s.replace("][", "]+[")
        #         check =1
        #     elif result3!=-1:
        #         s=s.replace(")[", ")+[")
        #         check =1
        #     elif result4!=-1:
        #         s=s.replace("](", "]+(")
        #         check =1

        #     if check == 0:
        #         break
        # print('result:', s)
        
        # # 2, 3 교체 작업 

        # while 1:
        #     check = 0
        #     result1 = s.find("()")
        #     result2 = s.find("[]")

        #     if result1!=-1:
        #         s=s.replace("()", "2")
        #         check =1
        #     elif result2!=-1:
        #         s=s.replace("[]", "3")
        #         check =1
            
        #     if check == 0:
        #         break

        # print('result?:', s)
        # tmpStack=[]
        # for i in s:
        #     tmpStack.append(i)
        
        # print('tmpStack', tmpStack)
        # p=[]
        # result = 0
# 괄호열이 뭔지? 

# 올바른 괄호열인지 체크 = 쌍을 이루는지 체크

# 닫기를 시작했을때, 계산시작 
# () = 2, [] = 3

# 다 닫고(if stack:) 새로 열때 +

from collections import deque

s = input()
stack=[]
check = 0
for i in s:
    if i =='(':
        stack.append('(')
    elif i == '[':
        stack.append('[')
    elif i == ')':
        if stack:
            if stack[-1]=='(':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check = 1
            print(0)
            break
    elif i == ']':
        if stack:
            if stack[-1]=='[':
                stack.pop()
            else:
                check = 1
                print(0)
                break
        else:
            check =1
            print(0)
            break
if check == 0:
    if stack:
        print(0)
    else:
        #print('좋다  시작하자 ')
        calStack=deque([])
        checkingStack = []
        cal=''
        result = 0
        
        for index, j in enumerate(s):
            if j=='(' or j=='[': 
                if index==0:
                    calStack.append(0)
                    checkingStack.append(j)
                else:
                    if s[index-1]==')' or s[index-1]==']':
                        checkingStack.append(j)
                        # print('된건가?')
                    elif s[index-1]=='(' or s[index-1]=='[':
                        checkingStack.append(j)
                    else:
                        checkingStack.append(j)
                        if calStack:
                            result = calStack[-1]
                            calStack.clear()
            if j==')':
                if s[index-1]=='(':
                    if checkingStack[-1]=='(':
                        print("들어오긴해?", index)
                        if index-2>=0:
                            if s[index-2]==')'or s[index-2]==']':
                                calStack[-1]+=2
                                print("나왔어?")
                            else:
                                if calStack[-1]==0:
                                    calStack[-1]+=2
                                    checkingStack.pop()
                                    print("!")
                                else:
                                    calStack[-1]*=2
                                    checkingStack.pop()
                                    print("!!")
                        elif index-1>=0:
                            calStack[-1]+=2
                            print("*")

                    else:
                        calStack.append(2)
                        result+=calStack[-1]
                        calStack.clear()
                        checkingStack.pop()
                        print("@")
                elif s[index-1]==')':
                    if calStack:
                        if len(checkingStack)==1:
                            v=sum(calStack)*2
                            calStack.clear()
                            calStack.append(v)
                        else:
                            calStack[-1]=calStack[-1]*2
                            checkingStack.pop()
                            print("??????????")
                    else:
                        result=result*2
                        checkingStack.pop()
                        print("#")
                elif s[index-1]==']':
                    if checkingStack[-1]=='(':
                        calStack[-1]=calStack[-1]*2
                        # result += sum(calStack)*2
                        # calStack.clear()
                        # checkingStack.pop()
                    print('?뭘까???')

                
            if j==']':
                if s[index-1]=='[':
                    if checkingStack[-1]=='[':
                        if index-2>=0:
                            if s[index-2]==')'or s[index-2]==']':
                                calStack[-1]+=3
                                # print("나왔어?")
                            else:
                                if calStack[-1]==0:
                                    calStack[-1]+=3
                                    checkingStack.pop()
                                else:
                                    calStack[-1]*=3
                                    checkingStack.pop()
                        elif index-1>=0:
                            calStack[-1]+=3
                        # print("!")
                    else:
                        calStack.append(3)
                        result+=calStack[-1]
                        calStack.clear()
                        checkingStack.pop()
                elif s[index-1]==']':
                    if calStack:
                        if len(checkingStack)==1:
                            v=sum(calStack)*3
                            calStack.clear()
                            calStack.append(v)
                        else:
                            calStack[-1]=calStack[-1]*3
                            checkingStack.pop()
                            # print("?")
                    else:
                        result=result*3
                        checkingStack.pop()
                    
                    #print('?뭘까')
                elif s[index-1]==')':
                    if checkingStack[-1]=='[':
                        # result += sum(calStack)*3
                        # calStack.clear()
                        # checkingStack.pop()
                        calStack[-1]=calStack[-1]*3

            print('checkingStack', checkingStack, ' result: ', result, ' calStack: ', calStack, 'index: ', index, ' j: ', j)

        for i in calStack:
            result+=i

        print(result)

 

알게된 것

 

해당 문제에 대한 접근법

'코딩테스트 > 문제 풀이 - Python' 카테고리의 다른 글

[백준9252] LCS 2  (0) 2024.08.14
[백준17829] 222-풀링  (0) 2024.08.14
[백준10799] 쇠막대기  (0) 2024.08.09
[백준2110] 공유기 설치  (0) 2024.08.06
[백준16918] 봄버맨  (0) 2024.08.05

https://www.acmicpc.net/problem/10799

 

 

 

문제 해결 방법

0. 카운트 변수를 만든다. 초기화는 0으로 한다. 

1. 받은 문장을 한글자씩 순회하며  현재 열려있는 괄호를 스택에 넣는다. (flag 표시를 해준다 flag = 1)

2. 문장을 순회하다가 닫는 괄호가 나오면 바로 직전에 본 글자가 열려있는 괄호인지 확인한다. (확인은 flag를 통해서 한다. )

 2-1. flag가 1이면 레이저로 확인되었으므로 그동안 열려있는 괄호의 수만큼 카운트 변수에 +1을 해준다. 

 2-2. flag가 0이면 단순 닫는 flag이니 괄호를 닫아주되(들어가있는 값중 -1 인덱스의 값이 열린괄호가 있으면 pop), 하나의 종이를 레이저가 한번 자르면 종이가 n개면 +1이 된다는 것을 고려하여 카운트값에 +1을 해준다.  

 

3. 순회가 끝날 때 까지 2번을 반복한다. 

 

 

 

코드 구현

정답 코드 

# ()는 레이저다 

s = input()
stack=[]
cnt = 0
flag=0
for i in s:
    if i =='(':
        stack.append('(')
        flag=1
    elif i==')':
        if stack[-1] =='(':
            stack.pop()
            if stack and flag==1:
                if flag ==1:
                    cnt+=len(stack)
            if flag ==0:
                cnt+=1
            flag=0


    # print('난 ',i,'에요', stack, 'cnt: ', cnt)
print(cnt)

 

시간/공간 복잡도

괄호 문자는 최대 100,000 이므로, O(NlogN)으로 풀어야한다. 

최적화 및 개선

따로 하지 않음 

어려웠던 점

없음

 

알게된 것

따로 없음 

[MVC(Model-View-Controller)]

:모델(데이터)-뷰(사용자가 보는 화면)-컨트롤러(사용자의 명령을 처리하는 역할)

 

*동작 과정

1. 클라이언트의 요청

2. 컨트롤러가 요청 처리: 컨트롤러가 사용자의 요청을 받는다. 

3. 모델에서 데이터 가져오기: 컨트롤러는 모델에서 데이터를 가져온다.

4. 뷰에 데이터 전달: 컨트롤러는 가져온 데이터를 뷰에 전달한다. 

5. 사용자에게 응답: 뷰는 모델의 데이터를 사용자에게 보여준다. 

 

결과적으로, 모델-뷰-컨트롤러(MVC)는 애플리케이션의 구조를 세 부분으로 나눠서 관리하는 방법이다. 이렇게 할 경우, 코드가 깔끔하게 유지되고 유지보수가 쉬워진다. 


 

1. Spring MVC Configuration

: 스프링은 WebMvcConfigurer라는 인터페이스를 제공해, 애플리케이션 개발자가 쉽게 MVC 설정을 커스터마이징할 수 있도록 한다. WebMvcConfigurer의 메서드를 확인해보면 어떤 항목을 설정할 수 있는지 확인할 수 있다. 

 

2. View Controller

: WebMvcConfigurer가 제공하는 addViewControllers 메서드를 통해 특정 요청에 대해 뷰를 응답하도록 설정할 수 있다. 

addViewControllers 메서드를 사용하면 컨트롤러를 작성하지 않고도 뷰(html 등)를 응답할 수 있다. 해당 메서드는 특정 URL을 특정 뷰에 매핑할 때 사용된다. 주로 정적 페이지(로그인 페이지나 에러 페이지 등)을 처리할 때 유용하다.

 

* 뷰 파일

: 뷰 파일은 일반적으로 src/main/resources/templates 디렉토리에 위치한다.

 

 

* 직접 Controller을 작성하는 방식보다 WebMvcConfigurer을 사용하는 방식이 더 효율적이라서 더 좋은 방식이라고 생각한다. 단순 정적 페이지를 제공해야 할 경우에 적합하다. 그러나 복잡한 비즈니스 로직이나 동적 데이터 처리가 필요한 경우 적합하지 않아 구현 기능이 제한될 수 있다. 또한 다양한 HTTP 요청 메서드(GET, POST, PUT, DELETE 등)을 처리하기에 부적합하다. 

 

3. Interceptor

:Spring Framework에서 Interceptor는 주로 HTTP 요청의 사전 처리와 사후 처리를 관리하는 데 사용되는 컴포넌트다. 

Interceptor을 사용해 컨트롤러로 요청을 전달하기 전이나 후에 특정 로직을 실행할 수 있다. (인터셉터는 컨트롤러로 들어오는 요청을 가로채고 처리할 수 있는 메커니즘을 제공한다)

 

WebMvcConfigurer가 제공하는 addInterceptor 메서드를 통해 특정 패턴에 대해 인터셉터가 동작하도록 설정할 수있다. 

*addInterceptor메서드는 Spring MVC에서 인터셉터를 추가하고 설정하는데 사용된다. 

 

여기서 CheckLoginInterceptor, MemberController 에 breakpoint 를 설정하여 디버깅을 진행하며 작동 순서를 확인해 보기 위해서 해보니까 너무 깊이 들어감; 원래 이런건지? 


스프링이라는 프레임워크가 HandlerInterceptor 라는 인터페이스를 제공하고, 이런 행위를 지원하는 이유는 무엇일까요? 

=> 사용자를 권한별로 다른 페이지를 보여주고싶은 경우.

 

=> 특정 조건별로 다른 페이지를 보여주고 싶은 경우

 

=>

  • preHandle: 요청 전 인증 및 권한 검사, 입력 유효성 검사
  • postHandle: 응답 생성 후 데이터 가공, 로깅
  • afterCompletion: 리소스 정리, 예외 처리 후 추가 작업

=> 코드 가독성 및 유지보수성 향상 

인터셉터를 사용하면 공통 기능을 분리하여 코드의 가독성과 유지보수성을 향상시킬 수 있다. 컨트롤러는 자신의 비즈니스 로직에 집중할 수 있으며, 인증이나 로깅과 같은 공통 기능은 인터셉터에서 처리한다.

 

=> 모듈화 및 재사용성

인터셉터는 모듈화된 방식으로 특정 URL 패턴에 적용할 수 있기 때문에 필요한 곳에만 적용할 수 있다. 이는 코드의 재사용성을 높이고, 특정 기능을 다양한 컨트롤러에 쉽게 적용할 수 있도록 한다.

 

=> 선언적 접근 제어

인터셉터를 사용하면 URL 패턴을 기반으로 접근 제어를 선언적으로 설정할 수 있다. 이는 보안 설정을 일관되게 유지하는 데 도움이 된다.


 

 

어떤 상황에서 Interceptor 를 사용할 수 있을까요?정하여 디버깅을 진행하며 작동 순서를 확인해 보세요.

Interceptor 사용 목적:

=>공통 로직의 중앙 집중화:

여러 컨트롤러에서 공통으로 수행해야 하는 작업을 중앙에서 처리할 수 있습니다. 예를 들어, 인증, 권한 검사, 로깅, 요청의 데이터 변환 등을 Interceptor에서 처리할 수 있습니다.

 

=> 요청 전후 처리:

요청이 컨트롤러에 도달하기 전에 (preHandle), 컨트롤러에서 처리된 후 뷰가 렌더링되기 전에 (postHandle), 그리고 뷰 렌더링 후 (afterCompletion) 각각 특정 작업을 수행할 수 있습니다.

 

=>비즈니스 로직과 인프라스트럭처 로직의 분리:

컨트롤러의 비즈니스 로직과 인증, 권한 검사 등의 인프라스트럭처 로직을 분리할 수 있습니다. 이는 코드의 가독성과 유지보수성을 향상시킵니다.

 

 

Interceptor를 사용할 수 있는 상황

=>인증 및 권한 검사:

사용자가 로그인되어 있는지 확인하거나, 특정 리소스에 접근할 권한이 있는지 확인합니다.

 

=> 로깅 및 감사:

요청 및 응답에 대한 로깅을 수행하여, 애플리케이션의 사용 현황을 추적합니다.

 

=> 성능 모니터링:

요청 처리 시간을 측정하여 성능을 모니터링하고, 병목 현상을 파악합니다.

 

=> 글로벌 설정 적용:

요청에 대해 공통적인 설정을 적용합니다. 예를 들어, 응답 헤더를 추가하거나 요청 데이터를 변환합니다.

 

=> 예외 처리:

예외가 발생하기 전에 요청을 중단시키고, 적절한 응답을 반환할 수 있습니다.

 

 

4. Argument Resolver

: Spring Framework에서 HandlerMethodArgumentResolver는 요청 데이터를 메서드의 매개변수로 변환할 때 사용하는 전략 인터페이스이다. 컨트롤러의 메서드가 호출될 때 매개변수에 전달할 객체를 생성하거나 조작하는 로직을 구현할 수 있다. 

 

예를들어, HTTP 요청의 특정 헤더를 객체로 변환하거나, 세션에서 사용자 정보를 가져와 매개변수에 주입하는 등의 작업을 수행할 수 있다. 

 

WebMvcConfigurer가 제공하는 addArgumentResolvers 메서드를 통해 커스텀 ArgumentResolver를 추가할 수 있다. 

 


 

스프링이라는 프레임워크가 HandlerMethodArgumentResolver 라는 인터페이스를 제공하고, 이런 행위를 지원하는 이유는 무엇이고, 어떤 상황에서 ArgumentResolver 를 사용할 수 있을까?

 

지원 이유

  • 유연한 매개변수 처리 방법 제공을 위함
  • 중복 코드 제거와 재사용성 증대를 위함
  • 코드의 가독성과 유지보수성 향상을 위함

사용 가능한 상황

  • 인증 정보, 세션 데이터, 요청 헤더, 커스텀 어노테이션 등을 매개변수로 받을 때 사용 가능한 상황임

 

 

 

'프레임워크 > Spring' 카테고리의 다른 글

Spring Core 1  (0) 2024.08.14
Spring JDBC(Java Database Connectivity) 1  (0) 2024.08.12
Spring mvc 3 (예외처리)  (0) 2024.08.05
Spring mvc 2 (CRUD API)  (0) 2024.08.05
Spring mvc 1 (static/template resource)  (0) 2024.08.03

https://www.acmicpc.net/problem/2110

 

 

문제 해결 방법

 

0. 카운트 변수를 생성한다 초기값은 0

1. 받은 활호문장을 순회하면서 현재 괄호를 열고있는게 몇개인지 계속 체크를 한다. 

2. 괄호를 닫게 될 경우가 2가지 가 있다. 

    2-1. 순회하면서 닫는 괄호가 나온 경우, 직전에 여는 괄호가 나왔는지 체크한다. 이것은 괄호를 순회하며 사전에 flag로 지속적으로 체크한다. (flag=1)

    2-2. 순회하면서 닫는 괄호가 나온 경우, 직전에 여는 괄호가 나오지 않은 경우, flag는 0인 경우이다. 

 

3. 2-1의 조건을 통과한 경우, 현재 열고있는 괄호의 갯수만큼 카운트 변수에 더해준다. 

    2-2의 조건을 통과한 경우, 

 

 

코드 구현

정답 코드 

import sys
input = sys.stdin.readline

n, c = map(int, input().split())

tmp=[]
for i in range(n):
    x = int(input())
    tmp.append(x)

tmp.sort()
print(tmp)

start = 1 # 최소 거리
end = tmp[-1]-tmp[0] # 최대 거리
cnt=1
result = 0
while start<=end:
    mid = (start + end) // 2 # 중간 간격 거리
    cnt = 1 # 첫번째 원소는 이미 방문한 것으로 침
    curr = tmp[0]

    for i in range(1, len(tmp)):
        if tmp[i] >= mid + curr: # 이동하려는 간격이 (중간간격 거리 + 현재 위치) 보다 크면
            cnt+=1
            curr=tmp[i]
        
    if cnt >= c: # 공유기 설치가 끝났으면, 범위를 좀 더 퍼트려줄 필요가 있다. 
        start=mid+1
        result=mid
    else: # 범위에 못미치면 범위를 줄여줄 필요가 있다. 
        end=mid-1

print(result)

 

 

시간/공간 복잡도

n^2으로 못풂

 

최적화 및 개선

  • 따로 하지않음

 

어려웠던 점

  • 상당히 낯설고 어렵게 느껴지는 문제이다. 
  • 이분탐색 알고리즘을 적용하고 싶은데 와닿는 이해가 되지 않는 문제였다. => 다음에 다시 풀어볼 문제

 

알게된 것

  • 이분탐색을 이렇게도 사용할 수 있다는점 
  • 이진탐색을 공유기 사이에 거리에 써야하는 것
  •  

 

1. Spring MVC 에서의 예외처리

: spring에서는 예외처리를 효과적으로 할 수 있도록 @ExceptionHandler, @ControllerAdvice 등의 어노테이션을 제공한다. 

 

2. @ExceptionHandler

: 특정 컨트롤러 내에서 발생할 수 있는 예외를 처리하기 위한 메서드에 적용되는 어노테이션이다. 

해당 어노테이션에 적용된 메서드는 해당 컨트롤러에서 처리되지 않은 예외를 캐치하고, 그 예외에 대한 사용자 정의 처리 로직을 실행한다. 

 

메서드는 예외 객체를 파라미터로 받을 수 있고, 적절한 응답을 반환할 수 있다. 

 

 

3. @ControllerAdvice

: 애플리케이션 전역에서 발생하는 예외를 처리하기 위한 클래스에 적용되는 어노테이션이다. 

해당 어노테이션을 사용하면 여러 컨트롤러에 걸쳐 공통적으로 발생할 수 있는 예외를 한 곳에서 처리할 수 있다. 

 

@ExceptionHandler와 같은 다른 어노테이션과 결합하여 사용되며, 특정 패키지 내의 컨트롤러 또는 특정 타입의 컨트롤러에 대해서만 적용할 수도 있다. 

 

==> 기존 코드 수정을 최소화 하면서 @ControllerAdvice를 통해 예외 처리를 할 수 있다. 

전역 예외 처리를 통해 컨트롤러의 코드를 수정하지 않고도 모든 예외를 중앙에서 처리할 수 있다. 

 

exception 폴더(예외를 정의해놓는 클래스를 모아놓은 폴더)에서 글로벌 예외 처리기 클래스를 하나 만든다. 

위 어노테이션을 통해 예외 처리 로직을 작성한다. 

 

 

1. CRUD API

CURD는 대부분의 소프트웨어가 가지는 기본적인 데이터 처리 기능으로, Create(생성), Read(읽기), Update(갱신), Delete(삭제)를 의미하는 말임. 리소스를 관리하는 일반적인 API를 만들때도 CRUD 기능을 구현한다. 

 

2. create

:리소스 생성을 요청하는 API. 서버에 데이터를 제출하기 위해 POST메서드를 사용함. 

리소스 생성 시 필요한 데이터를 json 형태로 body에 담아 요청을 보냄.

 

요청에 성공하면 201 응답코드로 응답받음.

Location 헤더에 생성된 리소스의 위치를 담아 응답을 받음.

생성된 리소스를 확인할 수 있도록 body에 생성된 리소스를 담아 응답할 수 있음

 

* ResponseEntity()

: 스프링 프레임워크에서 사용되는 클래스 중 하나로, HTTP 응답(response)의 전체 구성을 표현하는데 사용됨.

주로 컨트롤러에서 클라이언트로 반환할 HTTP 상태 코드, 응답 본문, 헤더 등을 포함할 수 있음.

이것을 통해 클라이언트로 정보를 받아 처리하기 위해서 일반적으로 @RequestBody 어노테이션과 함께 사용되며, 클라이언트가 보내는 JSON 또는 다른 형태의 자바 객체로 변환하여 받을 수 있다. 

 

* @RequestBody

: 어노테이션이고, HTTP 요청의 본문(body)을 자바 객체로 변환해주는 역할을 한다. 주로 JSON 형식의 데이터를 클라이언트에서 서버로 보낼 때 사용된다. 이 어노테이션은 스프링의 HttpMessageConverter을 통해 요청 본문을 읽고 자바 객체로 변환한다. 

 

사용방법: 컨트롤러 메서드 에서 @RequestBody를 사용해 요청 본문을 받아올 수 있다. 

 

 

3. read

: 리소스 조회를 요청하는 API이다. 서버에 리소스의 정보를 검색하기 위해 GET 메서드를 사용한다. 

== 즉, read메서드는 주로 클라이언트 입장에서 사용되는 메서드이다. 

클라이언트가 서버에 데이터를 요청하면, 서버는 이 요청을 처리하고 필요한 데이터를 반환한다. 

이 메소드의 역할은 서버가 가지고있는 리소스(데이터)를 클라이언트에게 전달하는것이다. 

(개발자 입장에서는 데이터를 반환하는 작업임)

 

요청에 성공하면 사용자는 200 응답코드를 응답 받는다. 조회된 정보를 body에 담아 응답할 수 있다. 

 

아까 ResponseEntity는  스프링프레임워크에서 HTTP응답을 표현하기 위한 클래스라고 설명했다. 

이는 데이터를 반환할 뿐만 아니라 데이터를 받을 때도 유용하게 사용될 수 있다. 

 

HttpStatus.OK

: 개발자가 ResponseEntity객체를 생성하여 데이터를 반환할 때, HttpStatus.OK도 함께 반환한다. 

이는 HTTP 상태 코드 200을 의미하며, 이는 요청이 성공적으로 처리되었음을 나타낸다. 

 

주요 HTTP 상태 코드 예시  

 

  • HttpStatus.OK (200): 요청이 성공적으로 처리되었음을 나타낸다. 
  • HttpStatus.CREATED (201): 요청이 성공적으로 처리되었으며, 새로운 리소스가 생성되었음을 나타낸다. 
  • HttpStatus.NO_CONTENT (204): 요청이 성공적으로 처리되었으나, 반환할 콘텐츠가 없음을 나타낸다.
  • HttpStatus.BAD_REQUEST (400): 잘못된 요청임을 나타냅니다. 클라이언트의 요청 구문이 잘못되었을 때 사용한다.
  • HttpStatus.UNAUTHORIZED (401): 인증되지 않은 요청임을 나타낸다. 
  • HttpStatus.FORBIDDEN (403): 서버가 요청을 이해했지만, 권한이 없어 거부되었음을 나타낸다. 
  • HttpStatus.NOT_FOUND (404): 요청한 리소스를 찾을 수 없음을 나타낸다.  
  • HttpStatus.INTERNAL_SERVER_ERROR (500): 서버 내부 오류가 발생했음을 나타낸다.  

4. update

:클라이언트가 리소스 수정을 요청하는 API. 리소스를 대체하기 위해 PUT 메서드를 사용한다. PATCH를 이용할 수 있으나, 전체 리소스를 대체하기 위해 PUT을 사용한다. 수정할 리소스의 식별자를 url path에 포함해서 요청을 보낸다.  body  값에는 수정할 정보를 담아서 보낸다. 

 

*@PutMapping

:웹사이트 안에서 바꾸고 싶은 특정 대상이 있는 경우 해당 어노테이션을 이용한다. 

 

*@PathVariable 

:URL에서 변수를 추출하여 메소드 파라미터로 전달한다. 통해 데이터를 가져온다. 

 

*@RequsetBody

: 요청 본문에 있는 JSON 또는 XML 데이터를 자바 객체로 변환하여 메소드 파라미터로 전달한다. 

 

 

5. delete

리소스 삭제를 요청하는 API. 리소스를 삭제하기 위해 DELETE 메서드를 사용한다. 삭제할 리소스의 식별자를 url path에 포함해서 요청을 보낸다.

 

*@DeleteMapping

: HTTP DELETE요청 처리를 위한 어노테이션.

해당 어노테이션을 사용하고, 반환값으로는 ResponseEntity<>(HttpStatus.NO_CONTENT)를 통해 적절한 상태코드를 반환해줘야 한다. 

 

 

https://www.acmicpc.net/problem/16918

 

문제 해결 방법

1. 시간이 1초씩 감에 따라서 봄버맨의 행동과 폭탄의 상태가 달라지니 시간을 변수로 둔다. 

2. 봄버맨의 움직임 패턴을 분석한다. 

종이에 쓰인 까만색깔 숫자는 시간 초를 의미한다. 

봄버맨은의 행동 패턴은 1초 단위로 다음과 같이 흐른다.

 

설치

->유지(현재 놓여진 폭탄에 대해서만 시간초를 매김=시간증가)

->설치와 동시에 현재 놓여진 폭탄도 시간초를매김 == 모든 좌표값을 +1 해준다.

->폭발

 

위 패턴을 폭탄 패턴이라 하겠다. 

 

그런데 이게 2초 부터는 폭탄이 설치 되어있지 않은 모든 칸에 또 폭탄을 설치하기 때문에, 2초부터 또다른 폭탄 패턴이 진행된다. 

 

따라서 0초부터 N초 까지는 직사각형 격자판에 2개의 폭탄 패턴이 돌고있다고 보면된다. 

 

이 2개의 서로 다른 초에 시작한 폭탄 패턴을 어떻게 패턴화 할까?

 

종이에 쓰인 폭탄 패턴을 0초, 1초는 제외해두고 2초부터 N초까지 보면,

 

증가와 설치, 폭발과 유지, 설치와 증가, 유지와 폭발 이 네가지 패턴이 반복됨을 알 수 있다. 

(유지와 폭발, 폭발과 유지는 폭발->유지로 친다. 왜냐하면 폭발이 우선순위이기 때문이다. )

 

이 네가지 패턴이 등장하는 숫자를 따로 모아보면, 

 

n%4 ==3, n%4 ==1, 이외의 경우로 정리할 수 있다. 

이렇게 정리된 경우를 잘 고려해 출력까지 잘 해주면 된다. 

 

코드 구현

틀린 코드

더보기
# 크기 RxC 격자 
# 폭탄칸 3초 지난후 폭발 => 폭탄 칸 파괴되어 빈칸됨 => 인접한 네 칸도 파괴됨
# 만약 폭탄 폭발시 인접 네칸에 폭탄이 있는 경우, 해당 폭탄은 폭발없이 파괴됨 = 연쇄반응X


# 1. 일부 칸에 폭탄 설치 = 모든 폭탄이 설치된 시간은 같음 0초
# 2. 다음 1초 동안 아무것도 하지 않음
# 3. 다음 1초 동안 폭탄이 설치되지 않은 모든 칸에 폭탄 설치=모든 칸은 폭탄을 가짐 =동시에 설치
# 4. 1초 지난 후, 3초 전에 설치된 폭탄이 모두 폭발
# 5. 3-4 반복
import sys

input= sys.stdin.readline # 뒤에 괄호 붙이지 않도록 조심.

dy=[0, 0, 1, -1]
dx=[1, -1, 0, 0]


r, c, n = map(int, input().split()) 

bombMap = []
for i in range(r):
    tmp = list(input().strip())
    bombMap.append(tmp)

print(bombMap)

time = 0
check=0
for i in range(r):
    for j in range(c):
        if bombMap[i][j] == '.':
            bombMap[i][j]=0
        else:
            bombMap[i][j]=1
print('초기상태')
print('check', check)
print('time: ', time)
for i in range(len(bombMap)):
    print('check', bombMap[i])
print()

realzero=1


while 1:
    
    time+=1
    check+=1
    
    if check==1:
        if realzero==1:
            realzero=0
            for i in range(r):
                for j in range(c):
                    if bombMap[i][j] > 0:
                        bombMap[i][j]+=1
        elif realzero ==0:
            for i in range(r):
                for j in range(c):
                    bombMap[i][j]+=1
    else:
        if check ==2 :
            for i in range(r):
                for j in range(c):
                    bombMap[i][j]+=1

            rem = [item[:] for item in bombMap]  
            if time >=5:
                for i in range(r):
                    for j in range(c):
                        if rem[i][j]==3:
                            bombMap[i][j]=0
                            for k in range(4):
                                ni = i + dy[k]
                                nj = j + dx[k] 
                                if  0<=ni<r and 0<=nj<c: 
                                    bombMap[ni][nj]=0
        elif check ==3:
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0

    if time >= n:
        print('check', check)
        print('time: ', time)
        for i in range(len(bombMap)):
            print('hello', bombMap[i])
        print()
        for i in range(r):
            tmpS=''
            for j in range(c):
                if bombMap[i][j]>0:
                    s='O'
                    tmpS+=s
                else:
                    s='.'
                    tmpS+=s
            print(tmpS)
        break

    rem = [item[:] for item in bombMap]  
    
    print('check', check)
    print('time: ', time)
    for i in range(len(bombMap)):
        print('hello', bombMap[i])
    print()

    if check ==3:
        check=1

 

정답 코드

# 크기 RxC 격자 
# 폭탄칸 3초 지난후 폭발 => 폭탄 칸 파괴되어 빈칸됨 => 인접한 네 칸도 파괴됨
# 만약 폭탄 폭발시 인접 네칸에 폭탄이 있는 경우, 해당 폭탄은 폭발없이 파괴됨 = 연쇄반응X

# 1. 일부 칸에 폭탄 설치 = 모든 폭탄이 설치된 시간은 같음 0초
# 2. 다음 1초 동안 아무것도 하지 않음
# 3. 다음 1초 동안 폭탄이 설치되지 않은 모든 칸에 폭탄 설치=모든 칸은 폭탄을 가짐 =동시에 설치
# 4. 1초 지난 후, 3초 전에 설치된 폭탄이 모두 폭발
# 5. 3-4 반복
import sys
input= sys.stdin.readline # 뒤에 괄호 붙이지 않도록 조심.

dy=[0, 0, 1, -1]
dx=[1, -1, 0, 0]

r, c, n = map(int, input().split()) 

bombMap = []
for i in range(r):
    tmp = list(input().strip())
    bombMap.append(tmp)

time = 0
for i in range(r):
    for j in range(c):
        if bombMap[i][j] == '.':
            bombMap[i][j]=0
        else:
            bombMap[i][j]=1

# 유지 time ==1
time+=1
for i in range(r):
    for j in range(c):
        if bombMap[i][j] > 0:
            bombMap[i][j]+=1

if n==1:
    if time == n:
        for i in range(r):
            tmpS=''
            for j in range(c):
                if bombMap[i][j]>0:
                    s='O'
                    tmpS+=s
                else:
                    s='.'
                    tmpS+=s
            print(tmpS)

else:
    while 1:
        time+=1
        
        if time % 4==1:
            # 유지
            for i in range(r):
                for j in range(c):
                    if bombMap[i][j] > 0:
                        bombMap[i][j]+=1
            rem = [item[:] for item in bombMap]  
            # 폭발
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0
        elif time % 4 == 3:
            # 폭발
            rem = [item[:] for item in bombMap]  
            for i in range(r):
                for j in range(c):
                    if rem[i][j]==3:
                        bombMap[i][j]=0
                        for k in range(4):
                            ni = i + dy[k]
                            nj = j + dx[k] 
                            if  0<=ni<r and 0<=nj<c: 
                                bombMap[ni][nj]=0
        else:
            # 설치, 증가
            for i in range(r):
                    for j in range(c):
                        bombMap[i][j]+=1


        if time == n:
            for i in range(r):
                tmpS=''
                for j in range(c):
                    if bombMap[i][j]>0:
                        s='O'
                        tmpS+=s
                    else:
                        s='.'
                        tmpS+=s
                print(tmpS)
            break

 

시간/공간 복잡도

 

시뮬레이션 문제 같아서 아예 시간복잡도 계산을 배제하고 문제를 풀었다. 

 

 

최적화 및 개선

  • 따로 하지않음

 

어려웠던 점

  • 이 문제는 문제를 풀 때 단순하게 생각하면 안된다. 
  • 시간을 보내는것과 보내는 중일때 할 일을 잘 구분해야한다. 
  • 처음에 폭탄 패턴이 두개 돌거란 생각을 하지 못했다. 따라서 문제 자체가 이해 안되고, 심지어 문제에 문제가 있는거 아닌가 하는 생각을 했었다. 항상 무조건 진행 과정에 대해서 헷깔리거나 파악이 안될때 종이에 해당 과정을 손으로 쓰면서 따라가보는게 중요한 것 같다. 
  • 규칙을 찾아야한다. 규칙을 찾는게 제일 중요한 것 같다. 

 

알게된 것

  • sys모듈의 sys.stdin.readLine는 코드로 작성할 때 괄호를 붙이면 안된다. 
  • sys.stdin.readLine을 사용할때, 뒤에 개행문자가 따라붙는다. 따라서 사용시 주의해줘야한다. 

 

개행문자를 제거하는 함수 사용시 참고할 부분이다. 

  • strip()이랑 rstrip() 무슨차이인가?
    • .strip()과 .rstrip()은 모두 문자열의 양 끝에서 공백 문자를 제거하는 메서드이지만, 제거하는 위치가 다름
      • .strip(): 문자열의 앞과 뒤 양쪽에 있는 모든 공백 문자를 제거한다.
      • .rstrip(): 문자열의 오른쪽(뒤쪽) 끝에 있는 공백 문자만 제거한다.

+ Recent posts