스프링 시큐리티는 사용하고자 하는 필터체인을 서블릿 컨테이너의 필터 사이에서 동작하기위해 DelegatingFilterProxy를 사용
DelegatingFilterProxy는 서블릿 컨테이너의 생명주기와 스프링 애플리케이션 컨텍스트(Application Context) 사이에서 다리 역할을 수행하는 필터 구현체이다.
표준 서블릿 필터를 구현하고 있으며, 역할을 위임할 필터체인 프록시(FilterChainProxy)를 내부에 가지고 있다.
필터체인 프록시는 스프링부트 자동 설정에 의해 자동 생성된다.
필터체인 프록시는 스프링 시큐리티에서 제공하는 필터로서 보안 필터체인(SecurityFilterChain)을 통해 많은 보안 필터(Security Filter)를 사용할 수 있다.
필터체인 프록시에서 사용할 수 있는 보안 필터 체인은 List 형식으로 담을 수 있게 설정되어 있어 URI 패턴에 따라 특정 보안필터 체인을 선택해서 사용하게 된다.
보안 필터체인은 WebSecurityConfigurerAdapter 클래스를 상속받아 설정할 수 있다.
필터체인 프록시는 여러 보안 필터체인을 가질 수 있어, 여러 보안 필터체인을 만들기위해서는 WebSecurityConfigurerAdapter 클래스를 상속받는 클래스를 여러개 생성하면 된다. 이때, WebSecurityConfigurerAdapter 클래스에는 @Order 어노테이션을 통해 우선순위가 지정되어 있는데 2개 이상의 클래스를 생성했을 대 똑같은 설정으로 우선순위가 100이 설정되어 있으면 예외가 발생하기 때문에 상속받은 클래스에서 @Order 어노테이션을 지정해 순서를 지정하는것이 중요하다.
만약 별도의 설정이 없다면, 스프링시큐리티는 아래와 같이 SecurityFilterChain에서 사용하는 필터 중 UsernamePasswordAuthenticationFilter를 통해 인증을 처리한다.
위 그림의 인증 수행 과정을 설명하면
1. 클라이언트로부터 요청을 받으면 서블릿 필터에서 SecurityFilterChain으로 작업이 위임되고, 그 중 UsernamePasswordAuthenticationFilter(위 그림에서 AuthenticationFilter에 해당)에서 인증을 처리한다.
2. AuthenticationFilter는 요청 객체(HttpServletRequest)에서 username과 password를 추출해서 토큰을 생성
3. AuthenticationManager에게 토큰을 전달. AuthenticationManager는 인터페이스이며, 일반적으로 사용되는 구현체는 ProviderManager 이다.
4. ProviderManager는 인증을 위해 AuthenticationProvider로 토큰을 전달
5. AuthenticationProvider는 토큰의 정보를 UserDetailsService에 전달
6. UserDetailsService는 전달받은 정보를 통해 데이터베이스에서 일치하는 사용자를 찾아 UserDetails 객체를 생성
7. 생성된 UserDetails 객체는 AuthenticationProvider로 전달되며, 해당 Provider에서 인증을 수행하고 성공하게 되면 ProviderManager로 권한을 담은 토큰을 전달
8. ProviderManager는 검증된 토큰을 AuthenticationFilter로 전달
9. AuthenticationFilter는 검증된 SecuritycontexHolder에 있는 SecuritryContext에 저장
위 과정에서 사용된 UsernamePasswordAuthenticationfilter는 접근 권한을 확인하고 인증이 실패한 경우 로그인 폼이라는 화면을 보내는 역할을 수행한다.
WAS(Web Application Server)는 웹 애플리케이션 서버를 의미하며, 클라이언트의 요청에 따라 동적인 웹 페이지를 생성하거나 비즈니스 로직을 처리하는 서버입니다. 주요 기능으로는 동적 컨텐츠 처리, 비즈니스 로직 실행, 웹 서버와의 연동, 세션 관리, 데이터베이스 연결, 보안 기능등을 제공합니다.
WAS의 예로는 Apache Tomcat, IBM WebSphere, Red Hat JBoss, Microsoft IIS 등이 있습니다.
Q. 서블릿에 대해 설명해보세요.
서블릿(Servlet)은 Java를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램입니다. 주로 HTTP 요청을 처리하고 응답을 생성하는데 사용됩니다. 서블릿의 주요 특징은 생명주기 관리, HTTP 요청 처리, 스레드 기반 처리, 상태 비저장(Stateless)이 있고 작동방식은 웹 서버와 함께 작동하는 웹 컨테이너(예: Apache Tomcat, Jetty) 내에서 실행됩니다.
Q. Spring Boot란?
Spring은 자바 기반 웹 애플리케이션 개발을 돕는 프레임워크이고, Spring Boot는 Spring을 더 쉽게 사용할 수 있게 하는 프레임워크
Q. IoC에 대해 설명해보세요.
IoC는 제어의 역전이라는 의미의 단어입니다. 객체의 생명주기 관리를 개발자가 아니라 IoC 컨테이너가 모두 맡아서 한다는 것이며, 실행의 제어권이 개발자에서 스프링 부트 프레임워크로 넘어갔다는 의미에서 제어의 역전이라고 부릅니다. IoC 컨테이너가 관리하는 객체들은 모두 싱글톤 패턴으로 관리되며, bean 객체라고 부릅니다.
Q. DI에 대해 설명해주세요
DI는 의존성 주입이라는 의미의 단어입니다. 클래스가 자신이 사용할 객체를 직접 생성하지 않고, IoC 컨테이너가 관리하는 bean 객체를 가져와서 사용하는 것 Autowired라는 어노테이션을 통해 DI를 설정할 수 있으며, 해당 대상이 IoC 대상으로 등록되어 있지 않다면 가져올 수 없습니다.
Q. REST API에 대해 설명해주세요.
자원을 이름 등으로 구분하여 해당 자원에 대한 데이터를 주고받는 시스템을 REST API라고 합니다. 정보를 요청할 때에는 GET, 정보를 추가 할때는 POST, 정보를 삭제할 때에는 DELETE, 정보를 수정할 때에는 PUT이나 PATCH 매핑을 사용합니다.
Q. MVC 패턴에 대해 설명해주세요
MVC 패턴은 사용자 인터페이스로부터 비즈니스 로직을 분리해서 서로 간의 영향을 받지 않도록 하는 소프트웨어 디자인 패턴입니다. DB 데이터를 가진 객체인 모델과, 화면을 처리하는 뷰와, 모델과 뷰를 연결하는 컨트롤러로 나눠집니다.
Q. AOP에 대해 설명해주세요
AOP는 관점 지향 프로그래밍이라는 의미의 단어입니다. 프로그램의 여러 지점에서 반복적으로 사용되는 코드를 분리해서 모듈화하는 것입니다. 핵심적인 코드와 부가적인 코드를 분리함으로써, 핵심 기능에 영향을 미치지 않으면서 부가적인 기능을 제공할 수 있습니다.
Q. 인터셉터와 필터, AOP의 차이에 대해 설명해주세요
필터 - 스프링에서 제공하는 기능이 아님 - WAS에서 동작 - 스프링 컨테이너의 가장 앞단에 있는 디스패쳐 서블릿에 요청이 도달하기 전과 후에 특정 작업을 수행하도록 함 - 스프링과 무관하게 전역적으로 처리하는 작업을 수행할 때
인터셉터 - 스프링 컨테이너에서 동작 - 스프링에서 제공하는 기능 - 디스패처 서블릿이 컨트롤러를 요청하기 전과 후에 특정 작업 수행
AOP - 필터와 인터셉터는 주소로 대상을 구분해야 하는데 AOP는 주소 외에도 어노테이션이나 파라미터 등 다양한 방법으로 구분할 수 있어서 비즈니스 로직 단에서도 활용할 수 있음
Q. AJAX 통신에 대해 설명해주세요.
자바스크립트를 사용해서 비동기적으로 서버와 데이터를 주고받는 기술 필요한 부분만 화면을 갱신하기 때문에, 화면전환이 발생하지 않습니다.
Q. JSON에 대해 설명해주세요
JavaScript Object Notation Key-Value 쌍의 데이터를 특정한 구조의 문자열로 표현해서, 데이터를 교환하는 형식 자바스크립트 객체의 형식을 기반으로 만들어진 것이기 때문에 JSON 형식을 쉽게 자바스크립트 객체로 변환할 수 있습니다.
Q. 템플릿 엔진이 어떤 것인지 설명해보세요.
지정된 템플릿 양식과 데이터가 합쳐져서 HTML 문서를 출력하는 소프트웨어입니다.
Q. CSR과 SSR의 차이에 대해 설명해주세요
SSR은 서버 측에서 렌더링을 처리하는 방식 서버에서 렌더링을 모두 마친 후 클라이언트에게 보내는 것입니다.
반대로 CSR은 클라이언트 측에서 렌더링을 처리하는 방식입니다. 사용자의 조작에 따라 특정 부분만 렌더링하는 비동기 통신이 CSR에 해당
Q. 디스패쳐 서블릿이란?
클라이언트가 어떤 요청을 했을 때 스프링 컨테이너의 제일 앞에서 요청을 먼저 받아서 적합한 컨트롤러에게 요청을 위임해주는 프론트 컨트롤러
URI에 포함되는 모든 글자는 리소스의 유일한 식별자로 사용되어야 하며 URI가 다르다는 것은 리소스가 다르다는 것이고, 역으로 리소스가 다르면 URI도 달라져야 합니다. REST API는 분명한 URI를 만들어 통신하는 것이 목표이기 때문에 혼동을 주지 않도록 URI 경로의 마지막에는 슬래시를 사용하지 않습니다.
A. 제가 생각하는 자바의 장점은 코드의 재사용성이 높다는 것입니다. 어떠한 기능을 수행하는 클래스를 만들고, 필요할 때마다 호출해서 사용할 수 있어서 코드도 간결해지고 같은 코드를 여러번 작성해야하는 수고가 덜어지기 때문에 장점이라 생각합니다.
Q. 객체지향 프로그래밍?
A. 객체지향 프로그래밍이란 객체와 객체간의 관계를 형성하거나 상호작용시킴으로써 프로그램을 구현하는 방법론입니다. 여기서 객체란 상태와 속성을 갖는 구체적인 객체입니다.
Q. SOLID 원칙
A.
S : 하나의 클래스는 하나의 책임만을 가져야 합니다. (단일 책임원칙) O : 클래스는 확장에는 열려 있어야하고, 수정에는 닫혀있어야 합니다. (개방/폐쇄 원칙) L : 상위 타입의 객체를 하위 타입으로 치환해도 프로그램은 일관성을 유지해야 합니다. (리스코프 치환의 원칙) I : 클래스는 자신이 사용하지 않는 인터페이스의 영향을 받지 않아야 합니다. (인터페이스 분리의 원칙) D : 상위 모듈은 하위 모듈에 의존해선 안되고, 하위 모듈이 상위 모듈에 의존해야 합니다. (의존 역전 원칙)
Q. 메서드 오버라이딩과 오버로딩?
A. 오버라이딩 : 부모 클래스의 메서드를 자식 클래스가 재정의해서 사용하는 것 오버로딩 : 같은 이름의 메서드가 서로 다른 매개변수들을 갖도록 여러 번 선언해서 사용하는 것
Q. 다형성에 대해 설명
A. 다형성은 하나의 타입에 여러 객체를 대입할 수 있게 해서 다양한 형태로 동작하게 하는 것 자료형을 다양한 형태로 바라봄으로써, 같은 코드에서 여러 실행 결과가 나타날 수 있게 합니다.
Q. 상속에 대해 설명해보세요
A. 상속이란, 부모 클래스의 멤버 변수나 메서드를 자식 클래스가 물려 받을 수 있게 하는 것 상속을 이용하면 부모 클래스의 기능을 유지하면서 새로운 기능을 추가하는 방식으로 기능을 확장할 수 있습니다.
Q. 업 캐스팅과 다운 캐스팅에 대해 설명해보세요
A. 업 캐스팅 : 다형성을 통해 자식 클래스를 부모 클래스 타입으로도 바라볼 수 있다는 것을 활용해서 자식 클래스의 생성자로 부모 클래스 타입의 변수를 생성하는 것 다운 캐스팅 : 부모 클래스의 참조를 자식 클래스의 참조로 변환하는 것, 업 캐스팅된 경우 자식 클래스의 멤버변수나 메서드에 접근할 수 없기 때문에 자식 클래스로 다운 캐스팅해서 사용
Q. 인터페이스에 대해 설명해보세요
A. 인터페이스는 기능을 명시적으로 선언하는 설계도이자, 강제성이 있는 약속. 해당 인터페이스를 구현하는 클래스가 어떤 메서드를 반드시 구현해야 하는지를 지시합니다. 멤버변수를 가질 수 없고 상수만 가질 수 있으며, 일반적으로 추상 메서드만 가질 수 있음 추상 메서드로 정의한 경우 해당 클래스에서 사용하지 않을 메서드도 반드시 재정의해야 하는데 필요한 경우에만 해당 메서드를 구현해서 사용하도록 하려면 default 키워드를 추가하면 됩니다.
Q. 가비지 컬렉션에 대해 설명해보세요
A. 가비지 컬렉션은 자바의 메모리 관리를 자동화하는 역할 가비지 컬렉션의 존재 덕분에 개발자는 객체의 메모리 해제에는 신경쓰지 않고 객체의 생성에만 집중할 수 있습니다. 하지만, 메모리가 해제되는 시점을 정확히 예측할 수 없다는 문제가 있습니다.
Q. 자료구조에 대해 설명해보세요
A. 자료구조는 많은 데이터들을 메모리 상에서 효율적으로 관리하는 구현 방법 자료의 효율적인 관리는 프로그램의 수행 속도와 밀접한 관련이 있기 때문에, 프로그램에 맞는 최적의 자료구조를 활용하는 것이 중요합니다. 대표적인 자료 구조로는 배열이 있습니다.
Q. 컬렉션 프레임워크에 대해 설명해주세요
A. 컬렉션 프레임워크는 데이터를 표현하고 조작하는데 사용되는 클래스와 인터페이스의 집합 대표적인 인터페이스로는 List, Map, Set이 있습니다. List는 순서가 있는 데이터를 저장, Map은 키와 값의 쌍으로 이루어진 데이터들을 저장하는 것 Set은 순서가 없는 데이터들을 저장하는 것
Q. JVM에 대해 설명해보세요 JVM의 단점은?
A. JVM은 자바 가상 머신의 약자, 컴파일러에 의해 변환된 .class 파일을 운영체제에 특화된 코드로 변환하여 실행하는 역할을 하는 가상의 실행 환경 JVM 덕분에 한 번 작성된 JAVA 코드는 어떤 플랫폼에서든 실행 가능하다는 장점이 있습니다. JVM의 단점은 실행속도가 느리고, 메모리 이슈가 있습니다.
Q. JRE란 무엇인가요?
A. Java Runtime Enviroment의 약자로 자바 애플리케이션을 실행하는 데 필요한 소프트웨어 구성요소들을 말합니다. 주요 구성요소는 JVM, 자바클래스 라이브러리, 자바 실행 환경이 있습니다.
Q. JDK가 무엇인지 설명하세요
A. Java Development Kit의 약자로 자바 프로그램을 개발, 컴파일, 디버깅, 실행하기 위한 환경 및 도구를 말합니다.
Q. 웹 컨테이너, 웹 서버, WAS에 대해 설명해주세요
A. 웹 컨테이너는 JSP와 서블릿이 실행될 수 있는 환경을 제공하는 컴포넌트 서블릿을 찾아서 실행하고, 서블릿으로부터 결과를 받아 클라이언트에게 전달합니다.
웹 서버는 클라이언트에서 전송된 HTTP 메시지를 다루는 서버입니다. 정적 리소스 요청만 처리할 수 있습니다.
WAS(웹 애플리케이션 서버)는 웹 서버가 처리하지 못하는 동적 리소스 요청을 처리해서 동적인 컨텐츠를 제공하는 서버
Q. 디자인 패턴에 대해 설명해보세요
A. 디자인 패턴은 공통적으로 발생하는 문제에 대해 자주 쓰이는 설계 방법을 정리한 패턴 개발자 간 소통이 원할해지고, 구조 파악이 용이하다는 등의 장점이 있습니다.
생성 - 구조 - 행위 패턴
Q. 제네릭 프로그래밍에 대해 설명해보세요
A. 제네릭 프로그래밍은 다양한 자료형이 적용될 수 있는 클래스를 만드는 것. 변수의 이름과 기능이 동일하면서, 자료형만 달라지는 변수들을 사용할 때 고려할 수 있습니다.
제네릭 프로그래밍은 코드에서 사용할 데이터 유형을 일반화하여 작성하는 아이디어입니다. 코드를 작성할 때 데이터 유형을 특정하지 않고 추상화하여 작성함으로써, 다양한 데이터 유형에 대해 유연하게 대응할 수 있게 됩니다.
Q. 람다 표현식에 대해 설명해보세요
A. 컴파일러가 데이터 타입을 추론할 수 있다는 점을 이용해서, 코드를 간소화하는 방법
익명 함수를 간결하게 표현하는 방법입니다. 주로 함수형 프로그래밍과 관련이 있으며, 코드를 간결하게 작성하고 병렬 프로그래밍을 지원하는데 사용
Q. 함수형 프로그래밍에 대해 설명해보세요
A. 함수를 기반으로 하는 프로그래밍 방식, 매개변수로 전달받는 입력 외에 외부 자료를 사용하지 않음
Q. 변수에 대해 설명해 보세요.
A. 변수란 값을 저장할 수 있는 메모리 공간
Q. 클래스와 객체에 대해 설명해보세요.
A. 클래스는 객체를 정의하는 설계도, 객체는 클래스를 기반으로 실제 메모리에 생성된 구체적인 대상
Q. 빌더 패턴에 대해 설명해보세요
A. 빌더 패턴은 객체를 생성하는 클래스와 객체를 표현한 클래스를 분리하는 디자인 패턴 멤버변수가 많은 객체를 다룰 때 빌더 패턴을 사용해서 코드의 가독성을 높이고 실수를 방지할 수 있습니다.
Q. 싱글톤 패턴과 프로토타입 패턴에 대해 설명해보세요
A. 싱글톤 패턴은 프로그램 내에서 해당 클래스의 객체가 단 하나만 생성되도록 하는 패턴 프로토 타입 패턴은 원본 객체를 새로운 객체에 복사, 필요에 따라 수정해서 사용하는 패턴
Q. 어댑터 패턴에 대해 설명해보세요
A. 어댑터 패턴은 호환되지 않는 클래스들을 함께 이용할 수 있도록 중간에서 맞춰주는 역할을 하는 인터페이스를 생성해서 사용하는 패턴
Q. final 변수와 static 변수의 차이점에 대해 설명해보세요
A. final 키워드는 변하지 않는 값을 저장하는 상수를 선언할 때 사용 static 키워드는 해당 클래스를 기반으로 만들어진 여러 객체들이 공유하는 하나의 변수가 필요할 때사용
Q. 자료형에 대해 설명해보세요
A. 자료형은 기본 자료형과 참조 자료형으로 나눠지고, 기본자료형은 정수형, 실수형, 문자형, 논리형으로 나눠집니다.
Q. Cll by Value와 Call by Reference 차이에 대해 설명해보세요
A. call by value는 값을 할당할 때 값 자체를 넘겨주는 방식 call by reference는 값이 담긴 변수의 주솟값을 넘겨주는 방식
Q. 깊은 복사와 얕은 복사의 차이에 대해 설명해보세요
A. 얕은 복사는 객체의 주솟 값을 복사 깊은 복사는 객체의 실제 값을 복사
Q. 자료 구조에서 스택과 큐의 차이에 대해 설명해보세요
A. 스택은 나중에 입력된 자료가 가장 먼저 출력되는 자료 구조 큐는 가장 먼저 입력된 자료가 가장 먼저 출력되는 자료 구조
Q. 스택 오버플로우에 대해 설명해보세요
A. 스택형 자료 구조에서 오버플로우가 발생하는 것을 말합니다. 오버플로우란 저장 용량을 초과하는 양의 데이터가 입력되었을 때 발생하는 현상 이는 기존 자료 중 일부가 지워지는 문제를 일으킬 수 있습니다.
Q. 프로세스와 쓰레드의 차이에 대해 설명해보세요
A. 프로세스는 메모리를 할당받아 실행되고 있는 프로그램이고, 쓰레드는 하나의 프로세스 안에서 실제 작업을 수행하는 작업 단위
Q. 인터페이스와 abstract class의 차이점?
abstract class는 추상 클래스라고 불리며, 하나 이상의 추상 메서드를 포함하거나 abstract 키워드를 가진 클래스 추상 클래스는 추상 메서드가 아닌 메서드도 가질 수 있으나, 인터페이스는 추상 메서드만 가질 수 있다는 차이점
Q. 배열과 리스트의 차이
A. 배열은 선언 시에 크기 지정 (정해진 크기의 메모리를 먼저 할당 받아 사용) 리스트는 선언 시에 크기를 지정하지 않아, 요소를 유연하게 관리할 때 사용
Q. 암호화 처리에 대해 설명해보세요
A. 데이터의 무결성/기밀성 확보를 위해 정보를 쉽게 해독할 수 없는 형태로 변환하는 기법
Q. 암호화 방식에 대해 설명해보세요
A. 1) 대칭키 암호 방식 (비밀 키 암호 방식) 암호화와 복호화에 같은 암호 키를 사용하는 방식 2) 비대칭키 암호 방식(공개 키 암호 방식) 누구나 알 수 있는 공개 키로 암호화된 메시지를 키의 소유자만 알 수 있는 비밀 키로 복호화해서 안전하게 통신하는 방식
Q. PKI에 대해 설명해보세요
A. PKI(공개 키 기반 기반 인프라, Public Key Infrastructure)는 컴퓨터 네트워크에서 안전한 통신과 전자적인 거래를 위한 암호화와 디지털 서명을 제공하는 기술적이고 기관적인 구조를 의미
Q. hash 알고리즘에 대해 설명해보세요
A. 값을 특수한 문자열로 암호화하는 단방향 암호화 알고리즘입니다. 단방향이기 때문에 암호화되고 나면 복호화할 수 없습니다.
Q. HashMap에 대해 설명해보세요
A. HashMap은 컬렉션 프레임워크에 속하는 key, value의 쌍으로 자료 구조를 구현한 클래스
Q. 접근 제어 지시자의 차이에 대해 설명해보세요.
A. 클래스 외부로부터의 접근 권한을 부여하는 명령어 public : 같은 프로젝트 내 어디에서든지 접근 가능 protected : 같은 패키지 내부거나, 다른 패키지라도 상속 관계라면 접근가능 default : 같은 패키지 내부에서만 접근가능 private : 같은 클래스 내부에서만 접근가능
Q. 캡슐화란 무엇인가요?
A. 객체의 데이터를 외부에서 직접 접근할 수 없도록 숨기고, 함수를 통해서만 접근 가능하게 하는 것
Q. 추상화란 무엇인가요?
A. 클래스를 정의할 때, 불필요하는 부분을 생략하고 객체의 속성 중 중요한 것들에만 중점을 두어 개략화하는 것
예) 자바에서는 동물을 나타내는 Animal 클래스를 만들 수 있습니다. 이 클래스는 동물이 가져야 하는 공통적인 특성과 동작을 정의합니다. 실제로 사용할 때는 구체적인 동물의 종류에 따라서 Animal 클래스를 상속받아 구현하면 됩니다.
Q. 생성자, getter, setter 메서드 코딩해보기
public class 클래스명 { // 기본 생성자 public 생성자명 () {}
// 사용자 정의 생성자 public 클래스명(자료형 매개변수1, 자료형 매개변수2, ...) { this.멤버변수1 = 매개변수1 this.멤버변수2 = 매개변수2 .... }
// getter public 반환자료형 get변수() { return this.멤버변수; }
핵심 구성요소 : StatelessWideget, AppBar, WebView, IconButton
플러그인 : webview_flutter: 3.0.4
📝 콜백 함수
콜백함수란 일정 작업이 완료되면 실행되는 함수를 말합니다.
함수를 정의해두면 바로 실행되지 않고 특정 조건이 성립될 때 실행되기 때문에 콜백이라고 합니다.
📝 코딩해보기
1) pubspec.yaml 설정
pubspec.yaml 파일은 플러터 프로젝트와 관련된 설정을 하는 파일입니다.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
webview_flutter: 3.0.4 // 여기에 추가
2) 안드로이드 설정
android/app/src/main/AndroidManifest.xml 파일 수정
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/> // 여기에 추가
<application
android:label="blog_web_app"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
3) build.gradle 파일 수정
android {
compileSdkVersion 32 // 여기 추가
namespace "com.example.blog_web_app"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.blog_web_app"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion 20 // 여기 추가
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
4) iOS 설정
// 아래 코드 추가
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSAllowArbitraryLoadsInWebContent</key>
<true/>
<dict/>
// 여기까지
</dict>
</plist>
5) 프로젝트 초기화
lib 폴더 위에 마우스 우클릭 > screen 폴더 생성 > 폴더 안에 home_screen.dart 파일 생성
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class HomeScreen extends StatelessWidget {
// 컨트롤러 변수 생성
WebViewController? controller;
HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// 배경색 지정
backgroundColor: Colors.orange,
// 앱 타이틀 설정
title: Text('준혁의 Blog'),
// 가운데 정렬
centerTitle: true,
// AppBar의 actions 매개 변수
actions: [
IconButton(
// 눌렀을 때 콜백 함수 설정
onPressed: () {
if (controller != null) {
// 웹 뷰에서 보여줄 사이트 실행하기
controller!.loadUrl('https://devjunyeok.tistory.com/');
}
},
// 홈 버튼 아이콘 설정
icon: Icon(
Icons.home,
),
)
],
),
body: WebView(
// 웹뷰 생성 함수
onWebViewCreated: (WebViewController controller) {
this.controller = controller; // 위젯에 컨트롤러 저장
},
initialUrl: 'https://devjunyeok.tistory.com/',
javascriptMode: JavascriptMode.unrestricted,
),
);
}
}
정상적으로 쿼리문을 작성하였다면 위 처럼 메세지가 나옵니다. 테스트용으로 데이터를 insert 하였으므로 ROLLBACK을 시켜줍니다.
ROLLBACK;
SQL Developer의 쿼리문을 ;을 제외한 모든 문장을 복사하여 @Query(value = " ")의 " " 사이에 붙혀넣기합니다.
// 게시물 저장함수 : 최초 생성(board_group(그룹번호)), board_parent(부모번호))
// => board_group(부모번호 == 자식번호(bid)), board_parent(최초생성시 0, 댓글이 달리면 부모 번호가 들어감)
// todo : JPA insert문 직접 작성(DML : 테이블 데이터 변경, 트랜잭션을 동반)
// => @Transactional, @Modifying
// => 예 ) 변수 전달 : :#{#replyBoard.boardTitle}
@Transactional
@Modifying
@Query(value = "INSERT INTO TB_REPLY_BOARD " +
"VALUES(sq_reply_board.nextval, :#{#replyBoard.boardTitle}, " +
":#{#replyBoard.boardContent}, " +
":#{#replyBoard.boardWriter}, " +
"0, sq_reply_board.CURRVAL, 0, 'N', " +
" TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'), NULL, NULL)", nativeQuery = true)
int insertByBoard(@Param("replyBoard") ReplyBoard replyBoard);
변수를 전달하기 위해 '제목', '내용', '홍길동' 부분을 아래와 같이 수정합니다.
ReplyBoardService에 함수 추가
// 게시물 저장
public int saveBoard(ReplyBoard replyBoard) {
int insertCount = replyBoardRepository.insertByBoard(replyBoard);
return insertCount;
}
ReplyBoardController에 함수추가
// 게시글 저장
@PostMapping("/reply-board")
public ResponseEntity<Object> createBoard(@RequestBody ReplyBoard replyBoard) {
try {
int insertCount = replyBoardService.saveBoard(replyBoard); // db 저장
return new ResponseEntity<>(insertCount, HttpStatus.OK);
} catch (Exception e) {
// DB 에러가 났을경우 : INTERNAL_SERVER_ERROR 프론트엔드로 전송
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
// 저장 함수 : 게시물 생성(부모글)
const createBoard = (data:IReplyBoard) => {
return http.post<IReplyBoard>("/normal/reply-board", data);
};
2-5) 저장함수 : 답변글 저장
// 저장 함수 : 답변글 생성(자식글)
const create = (data:IReplyBoard) => {
return http.post<IReplyBoard>("/normal/reply", data);
};
2-6) 수정함수
// 수정 함수
const update = (bid:any, data:IReplyBoard) => {
return http.put<any>(`/normal/reply-board/${bid}`, data);
};
2-7) 삭제함수 : 부모글 + 답변글 모두 삭제
그룹번호 : 부모글과 자식글은 모두 그룹번호가 같음
// 삭제 함수 : 게시물(부모글) + 답변글(자식글) 모두 삭제
// 그룹 번호 : 부모글과 자식글은 모두 그룹 번호가 같음
const removeBoard = (boardGroup:any) => {
return http.delete<any>(`/normal/reply-board/deletion/${boardGroup}`);
};
-- 계층형 쿼리 : level 의사(가상)컬럼(예 : 부모 1 ~ 2,3,4 ...)
-- START WITH BOARD_PARENT(부모컬럼) = 0 (최초시작값: 부모)
-- CONNECT BY PRIOR BID(게시판 번호) = BOARD_PARENT(부모 번호)
SELECT BID AS bid
, LPAD('└', (LEVEL-1))|| board_title AS BoardTitle
, board_content AS BoardContent
, board_writer AS BoardWriter
, view_cnt AS viewCnt
, board_group AS boardGroup
, board_parent AS boardParent
FROM TB_REPLY_BOARD
WHERE BOARD_TITLE LIKE '%%'
AND DELETE_YN = 'N'
START WITH BOARD_PARENT = 0
CONNECT BY PRIOR BID = BOARD_PARENT
ORDER SIBLINGS BY BOARD_GROUP DESC;
SQL Developer 에서 계층형 쿼리 실행 결과
댓글의 경우 └제목 으로 표시가 된것을 확인할 수 있음
📝 ReplyBoardDto.java
package com.example.simpledms.model.dto.normal;
/**
* packageName : com.example.simpledms.model.dto.normal
* fileName : ReplyBoardDto
* author : GGG
* date : 2023-10-26
* description : 계층형 쿼리 DTO
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-26 GGG 최초 생성
*/
public interface ReplyBoardDto {
// 속성 x => getter 함수
public Integer getBid();
public String getBoardTitle();
public String getBoardContent();
public String getBoardWriter();
public Integer getViewCnt();
public Integer getBoardGroup();
public Integer getBoardParent();
}
3) ReplyBoardService 생성
package com.example.simpledms.service.normal;
import com.example.simpledms.model.dto.normal.ReplyBoardDto;
import com.example.simpledms.repository.normal.ReplyBoardRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
/**
* packageName : com.example.simpledms.service.normal
* fileName : ReplyBoardService
* author : GGG
* date : 2023-10-26
* description :
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-26 GGG 최초 생성
*/
@Service
public class ReplyBoardService {
@Autowired
ReplyBoardRepository replyBoardRepository;
// 계층형 쿼리 조회(DTO) : like 검색
public Page<ReplyBoardDto> selectByConnectByPage(String boardTitle, Pageable pageable){
Page<ReplyBoardDto> page
= replyBoardRepository.selectByConnectByPage(boardTitle, pageable);
return page;
}
}
4) ReplyBoardController 생성
package com.example.simpledms.controller.normal;
import com.example.simpledms.model.dto.normal.ReplyBoardDto;
import com.example.simpledms.model.entity.basic.Dept;
import com.example.simpledms.service.normal.ReplyBoardService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* packageName : com.example.simpledms.controller.normal
* fileName : ReplyBoardController
* author : GGG
* date : 2023-10-26
* description :
* 요약 :
* <p>
* ===========================================================
* DATE AUTHOR NOTE
* —————————————————————————————
* 2023-10-26 GGG 최초 생성
*/
@RestController
@Slf4j
@RequestMapping("/api/normal")
public class ReplyBoardController {
@Autowired
ReplyBoardService replyBoardService;
// 전체 조회(계층형, DTO) : LIKE 검색
@GetMapping("/reply-board")
public ResponseEntity<Object> selectByConnectByPage(
@RequestParam(defaultValue = "") String boardTitle,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "3") int size
) {
try {
Pageable pageable = PageRequest.of(page, size);
Page<ReplyBoardDto> replyBoardDtoPage
= replyBoardService
.selectByConnectByPage(boardTitle, pageable);
Map<String, Object> response = new HashMap<>();
response.put("replyBoard", replyBoardDtoPage.getContent());
response.put("currentPage", replyBoardDtoPage.getNumber());
response.put("totalItems", replyBoardDtoPage.getTotalElements());
response.put("totalPages", replyBoardDtoPage.getTotalPages());
// 신호 보내기
if (replyBoardDtoPage.isEmpty() == false) {
// 성공
return new ResponseEntity<>(response, HttpStatus.OK);
} else {
// 데이터 없음
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
} catch (Exception e) {
log.debug(e.getMessage());
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}