8월 30, 2023

Spring 개발환경 세팅 - Spring Initializr 사용

오늘은 Spring 개발환경 세팅 방법에 대해 알아보도록 하겠다. 

 

https://start.spring.io/

 

위 링크에 들어가면 클릭 몇 번만으로도 Spring 프로젝트를 실행할 수 있도록 도와준다. 

 

 

여기서 Project는 Gradle - Groovy, Language는 Java, Spring Boot version은 원하는 것을 선택하면 된다. 

다만, Spring Boot 버전 뒤에 (SNAPSHOT)이라고 되어 있는 것은 정식 버전이 아니니, 이렇게 되어 있는 것은 제외하고 선택하도록 하자. 

 

그리고 Spring Boot version 3부터는 JAVA 17 버전이 필요하다고 하여, JAVA 11 버전을 사용하는 나는 2.7.14 version으로 선택해주었다. 

 


오른쪽의 Dependencies에 가서는 "ADD DEPENDENCIES" 버튼을 클릭해주자. 그리고 가장 필수적인 "Spring Web"과 "Thymeleaf" 두 가지의 dependency를 추가한다. 

 


 

이후 원하는 이름으로 Spring folder 및 package 이름을 설정하고 하단의 GENERATE를 클릭해주자. 

 

그러면 practice-spring.zip 이라는 zip 파일이 하나 생성이 될 텐데, 이를 압축을 풀고 Intellij에서 open file as project 클릭 후 



압축을 푼 폴더의 build.gradle이 있는 경로를 클릭해준다. 

 

그러면 자동으로 필요한 dependency를 다운받으면서 프로젝트가 생성이 된다. 

 

생성된 프로젝트에서 build.gradle 파일을 클릭하면 아래와 같은 소스가 자동 생성된 것을 볼 수 있다. 

plugins {
	id 'java'
	id 'org.springframework.boot' version '2.7.14'
	id 'io.spring.dependency-management' version '1.0.15.RELEASE'
}

group = 'spring'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '11'
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}

 

여기서 sourceCompatibility 란 Java 의 버전, 나는 Java 11을 사용했으니 11이라고 나오고 17 버전을 사용했으면 17 버전이라고 나올 것이다. 

 

dependencies에 보면 우리가 직접 추가한 thymeleaf와 web이 보이고, 마지막 줄에 test dependency는 자동으로 추가된 것을 알 수 있다. 

 

오늘은 여기까지 Spring Project 세팅하는 방법을 알아보았다. Spring 세팅이 생각보다 복잡한데 이 사이트 하나로 쉽게 세팅이 가능하니 참 편리한 것 같다. 


8월 30, 2023

Spring Web MVC 아키텍쳐 알아보기

[Spring] - 모델-뷰-컨트롤러(MVC) 패턴이란?

 

이전 포스팅에서 모델-뷰-컨트롤러라는 전체적인 MVC 패턴에 대하여 알아보았다. 오늘은 더 범위를 좁혀서 Spring Web MVC 아키텍쳐에 대하여 알아보도록 하겠다.

 

Spring Web MVC 아키텍쳐는 아래의 그림 하나로 요약이 가능하다. 



  

위 다이어그램에서 볼 수 있듯이 Srping Web MVC에서 핵심이 되는 것은 "DispatcherServlet"이다. 이는 등록된 요청 핸들러라고 할 수 있으며 요청을 전달하는 프론트 컨트롤러이다. 프론트 컨트롤러는 다른 컨트롤러들을 관리하는 최상위 컨트롤러라고 할 수 있다. 

 

각 핸들러는 서비스의 역할을 수행하며 DispatcherServlet이 요청을 전달할 뷰를 지정하게 된다. 


위 다이어그램을 통해서 Spring Web MVC에서 어떠한 과정으로 플로우가 흘러가는지 간단히 설명하도록 하겠다.

 

1. HTTP 요청 : 요청이 DispatcherServlet에 연결된다. 

 

2. 요청에 따른 컨트롤러 확인: DIspatcherServlet은 요청 경로에 따라 등록된 핸들러 중 어떤 핸들러가 호출되어야 할 지 결정하여 요청을 다시 전달하게 된다.

 

3. HTTP 요청: HTTP 요청을 핸들러가 처리하게 된다. 1번은 HTTP 요청이 DIspatcherServlet 즉 최상위 컨트롤러에 전달되어 어떤 컨트롤러에게 전달되어야 되는지 확인했던 과정이라면, 3번에서는 직접 연결되는 컨트롤러에 HTTP 요청이 전달된 것이라고 보면 된다. 

 

4. 모델 쿼리 업데이트: 핸들러는 모델에 대한 쿼리나 업데이트를 수행한다.

 

5. 모델 데이터 반환: 모델에서 필요한 작업을 수행한 뒤 모델 데이터가 반한된다. 

 

6. 모델 및 뷰 반환: 핸들러는 데이터와 논리 뷰 이름을 최상위 컨트롤러인 DIspatcherServlet으로 반환한다.

 

7. 뷰 이름으로 뷰 찾기: 논리 뷰 이름을 전달받은 DispatcherServlet은 뷰 이름을 확인하여 뷰 이름으로 뷰를 찾는다.

 

8. 모델 데이터 전달: 실제 뷰에 모델 데이터를 전달한다. 

 

9. 페이지 렌더링: 뷰에서는 모델 데이터를 전달받아 응답을 생성하고 DispatcherServlet은 응답을 클라이언트로 내보낸다. 

 


전반적인 개념 설정은 위와 같고, 실제 구체적인 예시를 통해 Spring Web MVC 응용프로그램을 만드는 것은 다음 포스팅에서 다루어보도록 하겠다. 


8월 30, 2023

모델-뷰-컨트롤러(MVC) 패턴이란?

오늘은 모델-뷰-컨트롤러 (Model-View-Controller) 패턴에 대해 알아보도록 하겠다. 이 패턴을 이해하고 나면 Spring Web MVC 패턴도 수월하게 이해할 수 있을 것이다. 

 

모델-뷰-컨트롤러(MVC)는 비즈니스 서비스와 도메인 객체(즉, 모델)를 UI(뷰)로부터 분리하고 하나 이상의 컨트롤러들 사이를 중재하는 아키텐쳐 패턴을 뜻한다. 비즈니스 로직과 도메인 객체를 변경하지 않으면서 UI를 변경하는 것이 용이해지며, 모델과 뷰 사이의 분리를 가능하게 해주는 패턴으로 유명하다. 

 

정보처리기사나 기술적 개념을 서술하는 시험에 단골로 등장하는 개념이기도 하기에 확실히 개념을 알아두는 것이 좋다.

 


MVC 패턴의 개념적 흐름도는 위와 같다.

JAVA 프로그램이 MVC를 실체화 하는 방식을 알면 위 아키텍쳐 패턴이 조금 더 이해가 잘 될 것이다.

 

자바 프로그램은 모델의 경우 비즈니스 계층의 코드 (ex) 서비스 Bean) 등을 사용, 뷰의 경우  JSP 등을 사용, 컨트롤러의 경우 서블릿 기반으로 구현을 하여 MVC 패턴을 실체화 한다. 


위 MVC 패턴의 스텝을 구체적으로 말해보면

1. HTTP 요청: HTTP 요청이 컨트롤러로 도착한다.

2. 모델에 대한 쿼리 또는 수정: 컨트롤러는 그 다음 모델에 접근한다.

3. 데이터 반환: 모델에 접근 후 모델로부터 데이터를 가져오거나 모델을 변경한다.

4. 뷰로 데이터 전달: 뷰를 사용하여 뷰로 데이터를 전달한다.

5. 페이지 렌더링: 뷰로부터 응답을 생성하고 적절한 데이터를 꺼낼 수 있도록 모델 전달 후 페이지 렌더링을 한다.

6. HTTP 응답: 클라이언트는 최종 생성된 응답을 수신하고, 서비스가 완료된다. 

 

즉 MVC 패턴은 단순히 말해서 관심사를 분리하는 것이라고 할 수 있다. 

 

다음 포스팅에서는 MVC 패턴 설명 기반으로 Spring Web MVC에 대해 다뤄보도록 하겠다. 


8월 30, 2023

[Spring] @Transactional 요소를 사용한 트랜잭션 정의

지난 글에서는

 

[Spring] Hibernate 객체 상태 구분 (transient, persistent, removed, detached)

 

HIbernate 세션으로 트랜잭션에 바인딩하는 과정, 그리고 그로 인해 변하는 객체 상태 구분에 대해 알아보았다. 

 

Spring의 트랜잭션 프레임워크가 이러한 과정을 대행해주기 때문에 개발자는 도메인 로직을 위주로 개발을 진행하게 된다. 그리고 프레임워크가 대부분의 일을 해주기 때문에 개발자는 트랜잭션의 경계가 어디까지이며 해당 트랜잭션 수행방식의 구체적인 옵션을 설정해주면 되는데, 오늘은 이와 같은 옵션들에 대해 알아보도록 하겠다. 

 

Spring의 @Transactional annotation을 사용하여 이러한 일을 해줄 수 있고 이러한 방식으로 트랜잭션을 지정하는 것을 선언적 트랜잭션 관리라고 한다. 

 

@Transactional 요소를 사용하여 트랜잭션을 정의할 수 있으며 사용할 수 있는 옵션에는 propagation, isolation, timeout, readOnly, rollbackFor, rollbackForClassName, noRollbackFor, noRollbackForClassName이 있다.

 

오늘은 이와 같은 옵션을 하나하나 세부적으로 알아보도록 하겠다.


 

1. propagation

propagation은 트랜잭션의 전파 동작을 정의하는 요소이다. 기본값은 REQUIRED 이며 기본값 이외에도 다양한 값을 지정할 수 있다.

  • REQUIRED: 기존에 이미 시작된 트랜잭션이 있으면 존재하는 트랜잭션에 참여하고, 기존에 시작된 트랜잭션이 없다면 새로운 트랜잭션을 시작하는 옵션. 특별히 언급이 없으면 REQUIRED 옵션이 기본값으로 사용된다.
  • MANDATORY: 기존 트랜잭션이 있다면 참여한다는 점에서 REQUIRED 와 유사하나, 기존에 시작된 트랜잭션이 없다면 새로운 트랜잭션을 시작하지 않고 예외를 발생시킨다. 독립적인 트랜잭션으로 수행되면 안되는 경우에 주로 사용한다.
  • NESTED: 이름이 의미하는 바와 같이 이미 수행되고 있는 트랜잭션이 있을 경우 중첩하여 트랜잭션을 진행한다. 
  • NOT_SUPPORTED: 이미 진행하고 있는 트랜잭션이 있다면 보류하고, 트랜잭션 없이 작업을 수행한다.
  • REQUIRES_NEW: 항상 새로운 트랜잭션을 시작하며, 기존 진행중인 트랜잭션은 보류한다.
  • SUPPORTS: 이미 진행중인 트랜잭션이 있다면 그 트랜잭션에 참여하고, 기존 진행중인 트랜잭션이 있다면 보류한다. 

2. isolation 

isolation은 트랜잭션의 격리 수준을 정의하는 옵션으로, 기본값으로는 DEFAULT를 가진다. 

이외에도 아래와 같은 다양한 옵션을 사용할 수 있다. 

  • DEFAULT: DB에서 기본 설정된 격리 수준을 따르는 옵션
  • READ_COMMITTED: Commit 된 데이터만 읽을 수 있는 옵션
  • READ_UNCOMMITED: 아직 Commit 되지 않은 데이터 또한 읽을 수 있는 옵션
  • REPEATABLE_READ: 트랜잭션이 종료될 때까지 그 영역에 해당하는 데이터를 다른 트랜잭션이 수정할 수 없는 옵션
  • SERIALIZABLE: 가장 높은 수준의 격리 수준을 가지는 옵션으로, 읽기 일관성 모드를 지원한다. 

DEFAULT 부터 아래로 내려갈 수록 격리의 수준이 점점 높아진다고 생각하면 된다. 


3. readOnly

readOnly는 트랜잭션이 읽기 전용인지를 판정한다. 기본적으로 readOnly의 기본값은 false이며, 설정을 통해 true로 설정할 수도 있다. 

메소드 위에 아래와 같이 annotation을 설정하면 readOnly의 값을 true로 설정할 수 있다.

@Transactional(readonly = true)

4. timeOut

timeOut은 트랜잭션의 제한시간을 초 단위로 정의하며 지정한 시간 내에 메소드가 수행완료되지 않으면 rollback된다. 

timeOut으로 정수의 값을 줄 수 있으며, 디폴트 값으로 timeOut의 값은 -1 이고 이 뜻은 시간 제한이 없다는 의미이다.


5. rollbackFor

특정 예외가 발생시 강제로 롤백을 시켜주는 옵션이다. 

@Transactional(rollbackFor=Exception.class)
public void addCompany(Company company) throws Exception {
	// 메소드 로직
}

이런식으로 사용할 수 있다. 


6. rollbackForClassName

rollbackForClassName 요소는 rollbackFor과 유사하지만 클래스 이름과 함께 사용된다는 점이 다른 점이다.


7. noRollbackFor

특정 예외가 발생시에 rollback 하지 않는다는 뜻이다. 위 rollbackFor과 동일하게

@Transactional(noRollbackFor=Exception.class)
public void addCompany(Company company) throws Exception {
	// 메소드 로직
}

위와 같이 사용할 수 있다.


8. noRollbackForClassName

noRollbackFor과 유사하지만 noRollbackForClassName은 클래스 이름과 함께 사용될 수 있다는 것이 다른 점이다. 


8월 30, 2023

[Spring] Hibernate 객체 상태 구분 (transient, persistent, removed, detached)

Hibernate에서 객체는 네 가지 상태로 구분된다. 임시(Transient), 영속(Persistent), 제거(Removed), 분리(Detached) 상태로 구분되며 다양한 Session API 메서드를 통해 개별 객체들에 대해 상태 전환 효과를 설정할 수 있다. 

 

각각이 무엇을 의미하는지 정의와 자세한 예시를 통해 살펴보자.


1. 임시(Transient) 상태

객체 저장 이전 즉 객체를 처음 생성한 상태를 의미한다.

new 연산자를 통해 객체를 처음 생성할 경우 새로 만들어진 자바 객체일 뿐 데이터베이스와 매핑되는 객체가 아니기 때문에 우리는 임시 상태의 Transient 상태라고 부른다. 

 

임시상태는 Hibernate가 관리하지 않는 상태라고 부르기도 한다. 

Hibernate가 관리하는 영속(Persistent) 상태로 전환시키기 위해서는

Session.save()

를 사용한다. 

 

Transient 상태의 예시를 알아보도록 하자.

Person person=new Person();
person.setName("홍길동");
person.setEmail("jjamonglemon@gmail.com");

이렇게 name과 email entity를 가지고 있는 Person class가 있고 그것을 통해 Person 객체를 하나 생성했다면 DB와의 매핑이 전혀 존재하지 않는 Transient 상태라고 볼 수 있다.

 

이 상태에서 위에서 언급한 Session.save() 메서드를 통해 객체를 저장하거나 다른 Persistent 상태의 객체로부터 참조화되는 경우 Persistent 상태로 전환된다고 볼 수 있다. 

 

또한 Transient 상태의 객체는 ID 값이 존재하지 않는다. 


2. 영속(Persistent) 상태

다음으로는 위에서도 언급한 Persistent 상태에 대해 알아보자.

위에서 언급한 Session save() 이외에도 saveOrUpdate(), persist() 메서드를 사용하여 저장되었거나 hibernate 조회 메소드 get(), load() 를 사용하여 생성된 경우에도 우리는 객체가 Persistent 상태에 있다고 지칭한다. 

또한 이미 persistent 상태에 있는 객체에 의해 참조된 경우에도 객체는 Persistent 상태로 분류된다. 

 

위 Person 객체를 Persistent 상태로 분류되게 만들기 위해 코드를 몇 줄 추가해보도록 하겠다.

먼저 Session.save()를 사용하기 위해서는 SessionFactory를 import해주어야 하는데 아래와 같은 코드를 자바 코드 맨 윗줄에 추가해주면 된다.

import org.hibernate.SessionFactory;

전체 코드로 살펴보도록 하겠다.

import org.hibernate.SessionFactory;

@Inject private SessionFactory sessionFactory;

Person person=new Person();
person.setName("홍길동");
person.setEmail("jjamonglemon@gmail.com");

Session session = sessionFactory.getCurrentSession();

session.save(person);

이런식으로 SessionFactory에서 currentSession을 가져와 거기에 Transient 상태인 person을 save하면 Persistent 상태로 바뀌는 것이다. 

 

Persistent 상태의 객체는 트랜잭션이 종료될 때까지 데이터베이스와 동기화되고 변경사항을 감지한다.

 

Session.save() 이외에도 get등을 통해 Persistent 객체를 만드는 코드를 살펴보자.

import org.hibernate.SessionFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class AccountServiceImpl implements AccountService{
	@Inject private SessionFactory sessionFactory;
    
    public void createAccount(Account account){
      	getSession.save(account); //account 객체 저장 persistent 상태
    } 
    
    public Account getAccount(Long id){
    	return (Account) getSession.get(Account.class, id); // Session의 get을 통해 Account 객체를 return 받음. 이것도 Persistent 상태
    }
    private Session getSession() {
    	return sessionFactory.getCurrentSession(); // current session을 받을 수 있는 메서드
    }
}

 

여기서 @Transactional annotation의 의미는 모든 public 메서드들이 트랜잭션을 지원한다는 것을 의미하는 것이다. 

 


3. 제거(Removed) 상태

말 그대로 삭제된 상태이며, 객체를 Persistent 영속성 context와 데이터베이스에서 모두 삭제하는 것을 의미한다.


4. 분리(Detached)상태

트랜잭션이 종료되어 세션이 닫힌 경우 세션으로부터 분리되었다고 하여 Detached 상태라고 부른다.

Session의 close() 메서드를 호출하면 DB와의 매핑이 사라지고 동기화가 해제된다. 즉 Persistent 상태에서 detached 된 것이라고 볼 수 있다. 

 


이렇게 위와 같이 Hibernate 객체의 네 가지 상태를 알아보았다. 따라서 트랜잭션의 큰 흐름은 아래와 같다.

 

1. Transaction 시작

2. Hibernate 세션 생성 후 Transaction에 바인딩

3. 서비스 메서드를 실행 (DAO 호출 포함 등등)

4. Hibernate 세션을 데이터베이스로 송출

5. Transaction commit/rollback

6. 리소스 해제, 후처리 (ex) Hibernate session close)


8월 30, 2023

Spring에서 Hibernate ORM 사용하는 방법

오늘은 Spring에서 Hibernate ORM을 사용할 수 있는 방법, Hibernate ORM 정의, 예시 등을 포스팅해보도록 하겠다.

 

먼저 ORM이 무엇인가부터 알아보아야 하는데,

ORM (Object Relationship Mapper)은 Persistence Framework의 일종으로 간단히 말해서 데이터베이스의 테이블과 객체를 매핑하는 방식이다. Persistence Framework란 영속성을 원활하게 도와주는 framework로, 프로그램이 종료되더라도 데이터를 저장하기 위해 DB와 연결하고 저장하는 방식을 도와주는 프레임워크라고 할 수 있다. Persistence Framework에는 아래와 같이 두 가지 종류가 있다.

  1. SQL Mapper: 말 그대로 SQL과 객체를 매핑하는 방식 ex) JdbcTemplate, MyBatis
  2. ORM: 데이터베이스의 테이블과 객체를 매핑하는 방식 ex) Hibernate ORM

ORM은 Java 객체와 DB 엔티티 사이를 오고가면서 변환을 해주고 Java 객체 사이의 관계와 DB의 관계 사이의 연결을 도와준다. 이러한 마찬가지 방법으로 쿼리 또한 연결해줄 수 있다.


이 중, Hibernate ORM은 데이터베이스의 테이블과 객체를 매핑해주는 ORM 방식의 한 종류라고 생각하면 된다.

 

Hibernate ORM은 JPA를 자바 코드로 구현해놓은 것이라고 생각하면 되는데, 여기서 JPA라는 개념이 새로 등장한다.

JPA (Java Persistence API)는 어려운 개념은 아니고 자바에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스일 뿐이다. 

 

예시로 이전 글과 같이 Person class를 Hibernate ORM으로 구현해보자.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.NamedQuery;
import javax.persistence.GeneratedValue;
import javax.persistence.GeneratedType;

@Entity
@Table(name="person")
@NamedQuery(
	name ="findPersonByEmail"
    query = "from Person where email like :email")
public class Person {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="id")
    private int id;
    
    @Column(name="first_name")
    private String firstName;
    
    @Column(name="last_name")
    private String lastName;
    
    @Column(name="email")
    private String email;
    
    public Person() {}
 
    public Person(String firstName, String lastName, String email) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    @Override
    public String toString() {
        return "Person [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]";
    }
}

 

코드 시작부분에 @Entity라는 annotation을 확인할 수 있는데 @Entity annotation을 통해 ORM에서는 Entity class를 만들어준다. Hibernate ORM은 자바 객체를 DB 테이블과 연결시킬 때 바로 연결시키지 않고 자바 객체를 Entity class로 변환시킨 뒤 그 뒤에 DB 테이블과 연결시킨다. 여기서도 @Entity를 통해 Entity class를 생성한 것이다. 

 

@table, @column은 각각 table과 column 과 연결시키겠다는 것이고, 

 

또 다른 annotation인 

@NamedQuery를 사용하여 JPQL (Java Persistence Query Language) 쿼리를 entity 기반으로 작성하면 된다. 

 

@GeneratedValue(strategy = GenerationType.AUTO)

위의 annotation을 사용하면 주 키를 어떤 방식으로 생성할 것인지 기술할 수 있다. GenerationType.AUTO라는 것은 primary key( 이 경우에는 ID) 가 자동생성되도록 지정하였다는 뜻이다. 

 

여기에는 대표적인 annotation 몇 개들만 소개를 했지만

java.persistence

패키지에 들어가면 더 많은 종류의 annotation들을 확인할 수 있다. 


8월 30, 2023

[Spring] JNDI를 사용한 DataSource 검색/ JNDI 정의, JDBC와 다른 장점

Spring에서 JNDI를 사용하여 DataSource를 검색할 수 있는 방법에 대해 알아보도록 하겠다.

 

1. JNDI란?

JNDI는 Java Naming and Directory Interface의 약자로, Java 언어를 사용하여 작성된 application에 이름 지정 및 디렉토리 기능을 제공해주는 API이다. 

 

우리가 흔히 알고 있는 JDBC와의 다른 점은 무엇일까?

JDBC는 DB에서 정보를 가져올 떄마다 매 번 DB연결을 열고, 닫고의 과정을 거치게 된다. 각 어플리케이션을 사용할 때마다 DB객체를 생성하고, connection을 연결하며 다 사용하면 connection을 종료하는 과정을 매번 거쳐야 하기 때문에 효율이 매우 떨어지는 방식이다. 이러한 이유로 상용되는 어플리케이션에서는 JDBC의 방식을 사용하지 않고 Pool 방식을 사용한다. 그리고 이러한 Pool 방식을 사용하는 예시로 JNDI 방식이 있다. 

 

JNDI는 어플리케이션이 아닌 WAS에 DB 커넥션 객체를 미리 네이밍해두는 방식인데 WAS 단에 미리 네이밍을 해둠으로써 DB 설정 정보를 파악하기 용이하며 효율적인 DB 커넥션 풀 방식을 이끌어낼 수 있다. 


 

2. JNDI기반으로 DataSource 검색하는 예시 

JNDI를 사용하여 DataSource를 검색하는 예시 코드를 살펴보자.

 

먼저 DB에 대한 이름 설정을 아래 web.xml 코드에서 설정해준다. 리소스에 대한 이름 등록은 <res-ref-name> 태그에서 해주게 된다. 

<resource-ref>
  <description>DataSource using JNDI example</description>
  <res-ref-name>jdbc/mydatabase</res-ref-name>
  <res-type>javax.sql.DataSource</res-type>
  <res-auth>Container</res-auth>
</resource-ref>

 

같은 xml 파일 또는 외부 설정 xml 파일에서 JNDI를 사용하여 검색을 수행할 수 있다. 

<jee:jndi-lookup id="dataSource" 
	jndi-name="jdbc/mydatabase" resource-ref="true" />

JNDI DatasSource 검색을 위해서는 jee 네임스페이스를 응용프로그램 안에서 정의해야 하며 

위 코드에서 볼 수 있듯이 jee:jndi-lookup 요소를 사용해야 한다.

 

위에서 id 특성은 해당 DataSource의 Bean이 노출될 ID를 지정하는 것이고 위 코드에서는 dataSource라는 아이디로 노출될 수 있도록 지정한 것이다. 

resource-ref="true"

위 코드는 해당 객체가 resource로서 응용프로그램 단위뿐 아니라 나아가 서버 scope에서 전역적으로 사용될 수 있게 한다는 뜻이다. 

 

위 설정을 지정하면 Spring에서는 JNDI 이름에 공통적으로 표준 접두사인

java:comp/env/

를 덧붙이게 되고 그러면서 전체 JNDI full 이름은 

java:comp/env/jdbc/mydatabase

가 되는 것이다. 


이렇게 JNDI 구성 방식을 사용하면 DataSource를 한 번만 만들고 여러 응용 application 프로그램 사이에 연결 풀을 공유하도록 할 때 효율적이고 용이하다는 장점이 있다.

 

또한 DB 연결 정보를 어플리케이션 단으로부터 독립시켜 구성할 수 있기 때문에 보안에도 안전하다. 


8월 30, 2023

[Spring] JDBC를 통한 데이터 접근, RowMapper 인터페이스, NamedParameterJdbcTemplate

Java Spring에서 관계형 데이터베이스를 통해 쿼리를 실행하고 데이터를 변경하고 싶다면 어떤 식으로 실행해야 할까? 이것을 구현하기 위해 알아야 하는 개념으로 RowMapper, NamedParameterJdbcTemplate 등이 있다. 

 

1. RowMapper

RowMapper란 JDBC 기본 인터페이스 ResultSet에서 원하는 객체로 타입을 변경해주는 역할을 한다. 

import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;

위와 같이 import를 해주면 RowMapper와 ResultSet을 사용할 수 있다. 

 

예를 들어, 

public class Person {
 private String firstName;
 private String lastName;
}

과 같은 class가 존재한다고 가정해보자. 

 

그리고

@Component
public class PersonRowMapper implements RowMapper<Person> {
	public Person mapRow(ResultSet resultSet, int rowNum) throws SQLException {
     Person person = new Person();
     person.setFirstName (resultSet.getString(1));
     person.setLastName (resultSet.getString(2));
     return person;
    }
}

위와 같이 PersonRowMapper class가 RowMapper 인터페이스를 구현했다고 가정하면, 아래와 같은 코드를 쓰는 것이 가능해진다. 

 

@Service
public class PersonServiceImpl implements PersonService {
  private static final String SEARCH_ALL =
  	"select first_name, last_name";
  
  @Inject private NamedParameterJdbcOperations jdbcTemplate;
  @Inject private PersonRowMapper personRowMapper;
  
  
public List<Person> getPeople() {
  return jdbcTemplate.query{
  	SEARCH_ALL, new HashMap<String, Object>(), personRowMapper;
  }
}
   
}

static final로 실행하고자 하는 select sql을 하나 만들었고 NamedParameterJdcbOperations를 통해 해당 sql를 실행한다. (jdbcTemplate.query 부분) 

 

해당 select sql을 실행하게 되면 select로 여러 값들이 반환될 텐데, 그 값들을 RowMapper를 사용하여 원하는 객체 형태인 Person 형태들로 변환이 되게 되는 것이다. 결국 getPeople 메서드에서는 personRowMapper를 통해 sql 실행한 값들을 원하는 Person 객체들로 받아 List<Person>으로 반환할 수 있게 되는 것이다. 


2. NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 에서는 변수 매핑을 ? 로 하던 기존 JDBC와는 달리 이름을 통해서 변수 매핑을 가능토록 만들어준다. 변수 이름 앞에  " : " 이것을 써서 가능하게 만들어준다. 

 

위 NamedParameterJdbcTemplate를 사용하여 Person 객체 하나를 더 만드는 메서드 하나와 기존 존재하는 Person 객체를 update하는 메서드 하나를 만들어보겠다. 

 

class는 위에서 사용하던 PersonServiceImpl과 동일한 class이고 위에서 사용한 코드에 추가하여 코드를 완성해보도록 하겠다. 

 

@Service
public class PersonServiceImpl implements PersonService {
  private static final String SEARCH_ALL =
  	"select first_name, last_name";
  
  //추가, createPerson 메서드에서 활용
  private static final String CREATE =
    "insert into person (first_name, last_name) " +
    "values (:firstName, :lastName)";
  //추가, updatePerson 메서드에서 활용
  private static final String UPDATE =
  	"update person set first_name = :firstName, " +
    				"where last_name = :lastName"; 
  @Inject private NamedParameterJdbcOperations jdbcTemplate;
  @Inject private PersonRowMapper personRowMapper;
  
  
public List<Person> getPeople() {
  return jdbcTemplate.query{
  	SEARCH_ALL, new HashMap<String, Object>(), personRowMapper;
  }
}
 // Create sql
 public void createPerson (Person person) {
  SqlParameterSource params= new MapSqlParameterSource()
  	.addValue("firstName", person.getFirstName())
    .addValue("lastName", person.getLastName());
  KeyHolder keyHolder= new GeneratedKeyHolder(); //자동생성되는 key 값을 위해
  jdbcTemplate.update(CREATE, params, keyHolder);
  
  //update
  public void updatePerson(Person person){
  	SqlParameterSource params= new MapSqlParameterSource()
  	.addValue("firstName", person.getFirstName())
    .addValue("lastName", person.getLastName());
    jdbcTemplate.update(UPDATE,params);
  }
 }
}

 

코드를 조금 더 자세히 살펴보면 createPerson 메서드에서는 데이터를 insert 하고 있는데, insert 명령문에서는 자동 생성되는 key 값을 내포하는 의미가 있으므로 GeneratedKeyHolder도 같이 생성하게 된다. 

 

여기서 살펴보아야 할 점은 NamedParameterJdbcTemplate를 사용하니 :콜론을 사용하여 자동으로 객체변환을 시켜주었다는 점이다. 

 

즉 데이터베이스 테이블 상 first_name, last_name (snake_case) 이었던 칼럼과 firstName, lastName (camelCase)로 자동매핑 되었다. 이와 같이 각각의 매개 변수 이름 앞에 콜론 기호를 접두사로 사용하여 자동매핑을 시켜주고 정확하게 의미를 구분하고 있다. 


8월 30, 2023

[Spring] @Autowired annotation 사용법

[Spring] p 네임스페이스, c 네임스페이스란? + ref 사용법

 

지난 포스팅에서는 p 네임스페이스와 ref를 사용하여 Bean에 DAO 등을 주입하는 방법을 알아보았다. 

오늘은 @Autowired 어느테이션을 사용하여 

p:[Dao name]-ref="daoName"

과 같은 특성을 더 이상 사용하지 않는 방법을 알아보도록 할 것이다. 


@Autowired를 사용할 수 있는 위치는 아래와 같이 세 가지가 있다.

  1. 생성자
  2. Setter
  3. 필드

각각의 방식에서 @Autowired를 사용할 수 있는 방법을 알아보도록 하자.


1. 생성자에 @Autowired를 사용하는 방법

 

@Service
public class AccountService {
 AccountDao accountDao;
 
 @Autowired
 public AccountService(AccountDao accountDao){
 	this.accountDao=accountDao;
 }
}

위와 같은 accountService class가 하나 있다고 가정해보자.

위의 @Autowired가 적용되기 위해서 AccountDao class는 어떤 형태를 띄어야 할까?

 

public class AccountDao {
 //
}

단순히 위와 같은 class로 짜여있으면 작동하지 않을 것이다. 

 

@Repository
public class AccountDao {
 //
}

위와 같이 @Repository annotation이 존재해야 작동하는데, 그 이유는 @Autowired가 의존 객체 type의 bean들을 찾아서 주입해주기 때문이다. @Repository annotation이 없으면 AccountDao를 bean으로 인식하지 못하여 의존성 주입에 실패하게 된다.


2. Setter에 @Autowired를 사용하는 방법

@Service
public class AccountService {

    AccountDao accountDao;
	
    @Autowired(required = false)
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
}
public class AccountDao {
 //
}

 

두 번째 방법은 위와 같이 setter method에 @Autowired annotation을 사용해주는 방법이다. 

여기서 

@Autowired(required = false)

위와 같이 required = false 옵션을 추가로 주게 되면 해당 bean이 없을 경우 주입받지 않을 수 있다는 뜻을 가진다. 만약 뒤에 required = false를 쓰지 않으면 기본값은 true 를 가진다. 즉 만약 requred = false가 추가로 들어가게 되면 해당 bean (여기서는 AccountDao) 이 호출조차 되지 않는다고 보면 된다. 


3. 필드에 @Autowired 를 명시하는 방법

@Service
public class AccountService {
	
    @Autowired
    AccountDao accountDao;
}

위와 같이 필드 자체에 @Autowired annotation을 사용하는 방법도 있다. 

 


여기서 만약 해당하는 형태의 Bean이 여러 개이면 어떻게 될까? 즉 만약 위와 같은 AccountService에서 Autowired로 연결된 AccountDao Bean이 여러 종류일 경우를 가정해보자.

 

public interface AccountDao {
	//AccountDao interface
}

@Repository
public class FirstAccountDao implements AccountDao {
  //
}

@Repository
public class SecondAccountDao implements AccountDao {
  // 
}

 

즉 위와 같이 AccountDao interface가 하나 존재하고 해당 인터페이스를 여러 Bean에서 객체로 구현을 할 경우이다. 여기서 FirstAccountDao, SecondAccountDao 모두 @Repository annotation이 있어, 둘 다 Bean으로 등록되어 있는 상황이다. 

 

이런 상황에서 AccountService class에서 아래와 같이

@Service
public class AccountService {
	
    @Autowired
    AccountDao accountDao;
}

AccountDao Bean을 @Autowired로 연결하면 어떤 AccountDao가 연결이 될까?

 

이 경우 스프링에서는 에러를 뱉는다.

왜냐하면 연결되는 Bean을 찾는데 두 개 이상이 나오게 되면 어떤 의존성을 주입해야 하는지 스프링에서는 알 길이 없기 때문이다. 


8월 30, 2023

[Spring] p 네임스페이스, c 네임스페이스란? + ref 사용법

Spring에서는 beans의 네임스페이스 기능을 사용하여 다른 형태의 속성 값 정의, beans 초기화 등을 가능하게 해주는데 오늘은 그것을 가능하게 해주는 기능 p 네임스페이스와 c 네임스페이스를 알아보도록 하겠다.

 

간단히 말해서 p 네임스페이스는 property, c 네임스페이스는 constructor-arg의 약자라고 할 수 있다. p 네임스페이스와 c 네임스페이스는 beans 네임스페이스와 달리, 모두 XSD 상에 정의되지 않았고, spring에서 제공하는 기능으로 구현된다. p 네임스페이스와 c 네임스페이스에 대해 각각 알아보도록 하자. 


1. p 네임스페이스

 

p 네임스페이스는 spring에서 다른 형태의 속성 값 정의를 가능하게 해준다. 

 

간단하게 p 네임스페이스는 아래와 같이 사용한다.

p:[속성이름]="value"

 

우리는 지난 포스팅에서 

 

[Spring] PropertyPlaceHolderConfigurer 사용 - 속성 외부 분리

 

dataSource의 properties 파일을 따로 만들어서 속성을 외부로 분리하는 방법에 대하여 알아보았다.

오늘은 p 네임스페이스를 사용하여 dataSource bean 속성을 정리해보자.

 

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
p:driverClassName="${dataSource.driverClassName}"
p:url="${dataSource.url}"
p:username="${dataSource.username}"
p:password="${dataSource.password}"/>

2. c 네임스페이스

 

p 네임스페이스와 달리 c 네임스페이스는 생성자 (costructor-arg)를 통해 bean을 초기화할 수 있는 방법을 제시한다. 

 

c 네임스페이스를 사용할 수 있는 방법은 p 네임스페이스와 마찬가지로 

c:[변수이름]="value"

이다. 

 

만약 

public Person (String name, int age){
	this.name=name;
    this.age=age;
}

를 생성자로 가지는 class Person이 있다면 우리는 c 네임스페이스를 사용하여 아래와 같이 생성자 매개 변수를 결정해줄 수 있다.

 

<bean id="Person" class="Person"
 c:name="John"
 c:age="20"/>

 

즉 name이라는 변수에 "John"이라는 값을 넣어주고 age라는 변수에 "20"이라는 값을 넣어주겠다는 것이다. 

 

만약 3rd party library를 사용하고 있어 매개 변수의 이름 variable name을 알 수 없다면 

c:_[변수Index]="value"

와 같은 식으로 적어주면 된다. 즉 아래와 같이 bean 정의가 가능한 것이다. 

<bean id="Person" class="Person"
 c:_0="John"
 c:_1="20"/>

c 네임스페이스와 p 네임스페이스는 모두 

-ref

접미사를 붙이게 되면 다른 Bean을 주입할 수 있다. 

 

 즉 만약

<bean id ="id..."
 class="bean class path...">
 <property name ="dataSource" ref="dataSource"/>
</bean>

라는 bean이 있었다면 

p:dataSource-ref="dataSource"

와 같이 적어줄 수 있다는 뜻이다. 


8월 30, 2023

[Spring] Bean의 범위 - 싱글톤, 프로토타입, 요청, 세션, 글로벌 세션

Spring에서 Bean을 정읳라 때 Bean의 범위 (bean scope)에 따라 컨테이너로부터 요청을 받는 떄에 따라 인스턴스가 만들어지고 관리되는 규칙을 정의할 수 있다. Bean의 범위에는 크게 다섯가지 유형: 싱글턴 (singleton scope), 프로토타입 (prototype scoe), 요청 (request scope), 세션 (session scope), 글로벌 세션 (global session scope)으로 구성되며 오늘은 각각의 유형에 대해 알아보도록 하자.


1. 싱글톤 범위 (singleton scope)

Spring에서의 Singleton은 Java의 싱글톤 디자인 패턴과는 차이가 있다. Spring에서의 싱글톤은 컨테이너 당 하나의 인스턴스만 존재한다는 것을 의미한다. 또한 Spring에서 scope을 따로 명시하지 않을 경우 디폴트로 적용되는 scope은 싱글톤이다. 

 

컨테이너로부터 싱글톤 스콥의 Bean을 처음 요청하게 되면 Spring은 인스턴스 하나를 생성하고 캐시에 보관하게 된다. 만약 그 이후에도 동일 Bean을 요청하면 Spring은 처음 요청 시 캐시에 보관한 인스턴스의 참조들을 반환한다고 생각하면 된다. 




위와 같은 이미지로 설명이 될 수 있다. 즉 싱글톤 범위의 Bean은 여러 클래스 인스턴스들 사이에서 공유된다고 할 수 있다. 싱글톤 범위를 사용하면 요청을 받을 때마다 객체를 만들어내는 overhead를 걱정하지 않을 수 있다는 장점이 있다.


2. 프로토타입 범위 (prototype scope) 

프로토타입 범위의 Bean은 Java에서 new 키워드로 객체를 생성하는 것과 비슷하다고 생각하면 된다. 프로토타입 범위로 설정한 bean은 설정 xml파일에서 getBean() 메서드가 호출되거나, 다른 Bean에 주입이 이루어질 떄마다 객체가 생성된다. 

 

즉 프로토타입 스콥의 Bean은 컨테이너로부터 요청을 받을 시 매번 새로 생성되어 컨테이너 밖으로 전달된다. 그러나 예외가 있다. 종속 관계로 이루어진 클래스가 싱글톤 범위인 경우 다시 생성되지 않고 캐시된 인스턴스가 주입되어 반환된다.

 

즉 하나의 종속성과 1:1 참조관계인 것이다. 이는 같은 종속관계더라도 계속 여러 참조가 같은 인스턴스를 사용했던 싱글톤과는 차이가 있다는 것이다. 

 

프로토타입 스콥을 도식으로 나타내면 아래와 같다.

 


 


이렇게 가장 중요하다고 할 수 있는 싱글톤 범위, 프로토타입 범위를 알아보았다. 다음 요청범위, 세션범위, 글로벌세션 범위는 웹 응용프로그램의 관점에 국한했을 때만 유용한 기능이다. 

 

3. 요청 범위 (request scope) 

요청범위로 설정되어 있는 Bean은 HTTP의 요청으로 주입이 발생할 때마다 객체가 생성되는 방식이다. 


4. 세션 범위 (session scope)

세션범위의 Bean은 표준 세션 범위 변수의 범위 안에서만 사용 가능한 스콥이다. 


5. 글로벌 세션 범위 (global session scope) 

포틀릿 응용프로그램의 관점에서만 적용 가능한 스콥을 글로벌 세션 범위라고 부른다. 


8월 30, 2023

[Spring] PropertyPlaceHolderConfigurer 사용 - 속성 외부 분리

Spring에서는 외부 속성 파일을 사용하여 분리된 환경 설정을 가능토록 해준다. 예를 들어 로컬 개발 데이터베이스에 개발자들이 연결을 하여 테스트를 실시한다고 가정해보자. 그럴 경우 DB 정보를 외부 속성 파일을 사용하여 분리된 환경 설정을 한다면 개발자가 접근할 필요가 없는 DB 정보 등은 서버 관리자가 따로 보관하는 것을 가능하게 해준다. 

 

Spring에서는 PropertyPlaceHolderConfigurer 클래스를 통하여 위와 같은 기능을 지원해줄 수 있는 API 클래스를 제공한다. 

 

이를 위해서 우리는 속성파일 (ex) springtest.properties)를 아래와 같이 만든다.

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/springtest?autoReconnect=true
dataSource.username=root
dataSource.password=test01!

 

위와 같이 PropertyPlaceHolderConfigurer properties 파일을 정의했다면 이제는 해당 Bean을 Spring 설정 파일에서 추가적으로 정의해주어야 한다. 

 

설정해주는 xml 파일에 아래와 같이 정의하는 것이다.

	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
	    <property name="location" value="springtest.properties"/>
	</bean>

 

위의 xml 파일 설정을 보면 id 특성을 설정하지 않았는데, 이 이유는 Spring 컨테이너 자체가 자동으로 객체의 존재를 확인하고 기능을 활성화시키기 때문이다. 

 

또한 value로 springtest.properties라고만 적고 class path 전체를 적어두지 않아도 식별이 가능한 이유는 해당 파일을 class path의 root에 위치시켰다고 가정했기 때문이다. (이 경우는 src/main/resources 경로에 springtest.properties 파일을 위치시켰다) 


그러면 이렇게 설정한 springtest.properties 파일을 dataSource bean에 값을 치환시키려면 어떻게 해야할까? 이 또한 설정 xml 파일에 작성해주면 된다. 

 

	<bean id="dataSource"
	  class="org.apache.commons.dbcp.BasicDataSource" 
	  destroy-method="close">
		<property name="driverClassName" 
		  value="${dataSource.driverClassName}"/>
	    <property name="url" value="${dataSource.url}"/>
	    <property name="username" value="${dataSource.username}"/>
	    <property name="password" value="${dataSource.password}"/>
	</bean>

위와 같이 적어주면 실행시에 properties 파일에 있는 url, username, password 의 값으로 치환되어 실행되는 것이다. 

 

여기서 추가로 

destroy-method ="close"

라는 부분이 의미하는 바는 bean 객체의 scope가 종료되었을 경우 해당 class의 close method를 호출하겠다는 뜻으로 이해하면 된다. 

 

이런식으로 DB의 속성을 외부로 분리하게 되면 여러 개발자들이 동일 프로젝트의 소스 코드로 테스트를 할 시 각기 다른 DB를 구축하여 동시에 시스템을 구축할 수도 있어 매우 유용하다. 


8월 30, 2023

XML 파일을 통해 Spring Bean을 정의하고 구성하기 (property, ref, ID, class)

지난 포스팅에서 Spring을 통한 의존성 주입과 제어역전의 개념을 알아보았다.


Spring 의존성 주입(DI), DI container, 제어의 역전 (IoC) 한번에 이해하기 

 

Spring을 이용하여 객체를 구성하고 의존성을 연결하는 여러 가지 방법들 중 가장 일반적인 방법은 XML, 그리고 선언적 방법이 있다. 

 

이번 포스팅에서는 Spring을 이용하여 XML 파일에서 Bean을 정의하고 구성하는 방법을 알아보도록 하자.

 

1. Bean ID, Class

<bean id="testBean" class="com.springPractice.TestBean" />

Bean의 ID는 Spring container에서 식별할 수 있는 이름, class는 해당 Bean의 Full 경로를 의미한다. 위 예제에서는 ID가 testBean, 그리고 해당 Bean의 full path 즉 com.springPractice.TestBean이 해당 Bean의 전체경로이다.

Spring에서는 ID 속성을 사용하여 Bean 사이의 종속성을 지원할 수 있다.


 

2. Bean Property

Bean xml에서

<property name="" value =""/>

이런식으로 property 속성을 통해 setter method를 타고 인스턴스를 정의할 수 있다.

 

이렇게만 보면 이해하기가 어렵기 떄문에 예제를 통해 설명을 해보도록 하겠다.

 

public class Person {
	//Person Class의 instance 
    
    private String name;
    private int age;
    
    public void setName(String name){
    	this.name=name;
    }
    public void setAge(int age){
    	this.age=age;
    }
}

 

위와 같이 name과 age를 인스턴스로 가지고 있는 Person class에 setter method로 setName과 setAge가 존재한다고 가정해보자. 그러면 우리는 Spring의 xml 파일에 property name, value 속성을 사용해서 setter method를 사용하는 것과 동일한 효과를 얻을 수 있는 것이다. 

 

xml 파일에서 Tommy라는 이름을 가진 25살 사람을 만들어준다고 해보자. 원래였다면 setter 메소드를 사용하여 주입해주었겠지만 우리는 아래와 같이 작성할 수 있다.

 

<bean id="person" class="com.SpringPractice.person">
	<property name="name">
    	<value>Tommy</value>
    </property>
    <property name="age" value="25"/>
</bean>

 

즉 property name에는 instance의 이름, value에는 실제 set 하려는 값을 적어주면 된다.


 

3. ref 

Ref 특성은 Spring을 이용하여 의존성 주입을 할 때 사용된다. value 특성과 다른점은 무엇일까. vlaue 특성은 값 객체 속성이나 기본 형식의 데이터를 주입하기 위하여 사용되는 반면, ref의 경우 참조형으로 id 혹은 name을 가지는 bean을 생성자의 인자로 넘겨주겠다는 의미로 이해하면 된다. 

 

즉 예를 들어 Service.java라는 파일 안에 testDao instance가 있다면,

<bean id="Service">
  <property name ="testDao" ref="firstDao"/>
</bean>

 

위와 같이 testDao instance에 firstDao를 서비스로 의존성 주입한 것이라고 생각하면 된다. 


이런식으로 Spring에서 xml 파일을 이용하여 의존성을 주입하는 방법, 그리고 그 속성들에 대하여 알아보았다.


8월 30, 2023

JAVA indexOf 활용법 (indexOf 위치, lastIndexOf)

오늘은 자바 소스에서 자주 보이는 
Java indexOf 문법 활용법에 대해 알아보도록 하겠다. 

1. 찾고자하는String.indexOf (String s) 

indexOf 함수를 쓰고자 할 떄는 내가 찾고자 하는 문자열과 준비된 문자열 이렇게 두 개가 있어야 한다. 

예를 들어, 

 

class Main{
	public static void main(String args[]){
    	String toBeFound = "apple"; //포함되어 있는지 확인할 문자열
        String wholeWords = "orangeapplebanana"; //전체 문자열
        
        System.out.println(toBeFound.indexOf(wholeWords)); //6
        System.out.println(toBeFound.indexOf("Helloooo"); //-1
    }
}

예를 들어, 위 예제를 보면 이해가 잘 될텐데, 
여기서 apple (toBeFound)은 우리가 찾고자 하는 문자열이고 
찾고자 하는 대상은 orangeapplebanana (wholeWords)이다. 

indexOf 함수는 특정 문자나 문자열에서 해당하는 문자의 인덱스 값을 반환한다. 

따라서 첫 번째 예제인, 

 

System.out.println(toBeFound.indexOf(wholeWords)); //6

여기서는 apple이 가장 먼저 시작되는 6번째 자리를 가져오게 된다. 

Java는 0번째 index부터 따지게 되고, indexOf의 경우 찾으려는 문자열이 1자리가 아니더라도 가장 처음 자릿수를 가져오게 된다는 점을 기억하면 된다. 

 

indexOf의 경우에는 찾고자 하는 문자열이 없을 경우 -1 을 return 한다는 점을 활용하면 찾고자 하는 문자열이 여러개이더라도 쉽게 이를 걸러낼 수 있다. 

 

class Main{
	public static void main(String args[]){
        System.out.println("[banana, apple]".indexOf("grape")); //-1
    }
}

 

위 코드와 같이 내가 전체 String에서 banana와 apple이 둘다 없는지를 검사하고 싶다면 코드를 두 줄로 쓰지 않고 한 줄로 가능하다. 

 

"grape"라는 String에 banana도 없고 apple도 없다면 -1을 return 할 것이기 때문이다. 

 

이런식으로 편리하게 로직을 짜서 사용할 수 있다. 


 

2. indexOf ("찾을 문자열" , "시작위치")

 

첫 번째 indexOf에서는 특정 String을 찾더라도 가장 처음 위치를 반환한다. 즉 "a,b,c,d"라는 string에서 내가 ","의 위치를 찾고 싶을 때 가장 처음 쉼표의 위치인 1을 return하는 것이다. 

 

만약 indexOf 의 두 번째 인자의 시작위치를 주게 되면 시작위치부터 문자열을 찾게 된다. 


3. lastIndexOf("찾을 string ") /  lastIndexOf("찾을 문자열", "시작 위치")

기본적으로 lastIndexOf는 indexOf와 사용법은 유사하나 

String의 뒤에서부터 탐색을 시작한다는 것이 차이점이다. 

 

뒤에서부터 문자열 탐색을 시작하더라도 index는 마지막 자리가 0인 것은 아니다. 

class Main{
	public static void main(String args[]){
        System.out.println("o".indexOf("Helloo"); //5
    }
}

이 경우 string의 가장 마지막부터 탐색을 시작하기 때문에 가장 마지막 o를 가져오는데 

index의 경우에는 H가 0부터 시작한다는 점은 동일하다.

 

따라서 가장 마지막 o의 index인 5를 가져오게 된다. 


8월 30, 2023

Spring 의존성 주입(DI), DI container, 제어의 역전 (IoC) 한번에 이해하기

Spring 프레임워크를 이해하는 것은 스프링프레임워크의 핵심이 의존성 주입 컨테이너라는 사실을 아는 것과, 

다른 모듈들이 작동하기 위하여 필요한 기능을 스프링의 의존성 주입 컨테이너가 제공한다는 사실을 아는 것에서 출발한다.

 

의존성 주입은 어려운 개념은 아니지만 개념을 완전히 이해해야 향후 스프링을 이해하는 것에도 문제가 없다.

 

1. 의존성 주입 (Dependency Injection)

스프링의 가장 기본 뼈대가 되는 의존성 주입이라는 개념에 대해서 알아보자.

 

먼저 "의존성"이란 한 객체가 다른 객체를 사용할 떄 의존성이 있다고 말한다. 

장난감을 주로 팔고 있는 상점을 예시로 생각해보자.

 

public class Store {
	private Toy toy;
}

 

그러면 위와 같이 Store이라는 class 안에 Toy라는 instance를 가지고 있는 구조를 띌 것이다.

 

이럴 떄 우리는 Store가 Toy에 의존성이 있다고 말한다. 즉 Store라는 class가 Toy class를 사용할 때 의존성을 가지는 관계라고 말을 하는 것이다. 

 


 

의존성을 가지고 있을 경우, 우리가 코드를 쓰는 가장 간단한 방법은 아래와 같다.

 

public class Store {
  private Toy toy;
  
  public Store() {
   this.toy= new Toy();
  }
}

 

하지만 위 클래스의 문제는 무엇일까?

 

바로 Store class와 Toy class가 강하게 결합되어 있고 의존성이 높다는 점이다. 만약 Store에서 판매하는 객체의 종류가 달라진다면, Store class 밖에서 무엇인가를 변경하는 것이 아닌, Store class의 생성자 자체를 변경해야 하는 상황이 오게 된다. 

 

이러한 의존성을 제거하기 위해서 Spring에서 자주 쓰는 기법으로

의존성 주입 (Dependency Injection : DI) 라는 개념을 소개하도록 하겠다.

 

의존성 주입이란 객체를 직접 생성하는 것이 아니라, 외부에서 생성한 후 주입시켜 주는 방식으로, 의존성을 낮추어주는 방식을 말한다.

 

위 예시에서는 Store에서 판매하고 있는 상품의 종류가 바뀔 수도 있는 경우를 대비해 

Store안에서 바로 객체 Toy를 생성하는 것이 아니라 외부에서 생성한 후 이를 주입해주어야 한다. 

 

이를 위해서는 Toy의 상위 개념 Product Interface를 하나 생성할 것이다.

 

public interface Product {
 //Toy의 상위 interface
}

public class Toy implements Product {
//Product interface를 구현한 Toy class
}

 

이렇게 상위 Interface를 생성한 뒤, Store class에서 바로 Toy 객체를 생성하는 것이 아니라 

상위 인터페이스로 Product 객체를 생성하는 것이다.

 

public class Store{
 private Product product;
 
 public Store (Product product){
 //생성자를 Toy 객체가 아닌 Product 객체로 받음
 	this.product=product;
 }
}

 

Spring container가 관리하는 객체를 빈 (Bean)이라고 부르며, 이러한 bean들을 관리한다는 의미로 컨테이너를 Bean Factory라고 부른다. 이는 디자인 패턴 중 Factory Design 패턴과도 연관이 있다.

 

따라서 BeanFactory라는 이름의 class를 생성하여 어떻게 의존성이 주입되는 것인지 살펴보겠다. 

 

public class BeanFactory{
	public void create(){
    	Product toy=new Toy(); //Bean 객체 생성
        
        Store store=new Store(toy); //생성자를 통한 의존성 주입
    }
}

위와 같은 방법으로 어플리케이션 실행 시점에 필요한 Bean이 생성되었고 해당 객체를 주입하고자 하는 객체에 주입하는 역할로 BeanFactory, 즉 DI container가 사용되었다. 


2. 제어의 역전 (Inversion of Control)

그리고 제어의 역전(Inversion of Control) 우리가 흔히 부르는 IoC라고 불리는 개념도 의존성 주입이라는 개념으로부터 파생된다.

어떠한 객체를 사용할지에 대한 책임 (ex 위 예시에서 Toy 객체) 은 프레임워크에게 넘기고, 자신은 수동적으로 주입받는 객체를 사용하기 때문이다. 제어가 프레임워크에 넘어갔고, 제어가 역전되었다는 의미로 제어의 역전이라는 용어를 이해하면 어렵지 않게 이해할 수 있을 것이다. 


오늘 포스팅의 예시는 Spring 에서 의존성 주입을 받는 방법 중 한 가지 예시이다. 이외에도 방법이 있으며 오늘 포스팅을 통해서는 대강 흔히 말하는 의존성 주입, 제어의 역전이 어떠한 개념적인 의미를 가지는지를 이해하는 것이 중요하다.


8월 30, 2023

[Python] Split 함수에 대해 알아보자 (sep, maxsplit 예제)

파이썬 문자열을 특정 기준으로 나누어 리스트로 만들수 있는 함수가 있다. 

가장 대표적으로 예시를 생각해보았을 때, 만약 문자열이 띄어쓰기를 기준으로 "a b c d z k" 이런 식으로 있다면 우리는 이 문자열을 띄어쓰기를 기준으로 쪼개서 각각 list의 0번째에는 a가, list의 1번째에는 b가 2번째에는 c가... 

이렇게 들어갈 수 있도록 만들고 싶은 것이다.

 

이처럼 파이썬은 이와 같은 경우에 split 함수를 사용한다. 

 

split 함수도 간단해보이지만 parameter에 무엇이 들어가냐에 따라 조금씩 사용법이 달라지게 된다.


1. 문자열.split() => 디폴트로 띄어쓰기와 엔터에 의해 구분되는 경우 

가장 대표적으로 split 함수를 적용할 수 있는 경우이다. 

어떤 기준에 의해 split 되는지가 명시되어 있지 않은 경우에는 default로 띄어쓰기와 엔터에 의해 구분된다. 

 

즉, 위에서 예시로 들었던 것과 같이 

s= "a b c d e f g"
div= s.split()

이라고 했을 경우 div 에는 각각 a/b/c/d/e/f/g 로 쪼개진 배열이 만들어진다고 생각하면 된다.


2. 문자열.split( '구분자' ) => 원하는 구분자로 split하고 싶을 경우

 

반면 모든 문자열이 띄어쓰기로 구분이 되어있지 않을 수도 있다. 예를 들어 "a/b/c/d"라는 문자열이 있다면 우리는 "/" 에 의해 구분하고 싶을 것이다. 이처럼 띄어쓰기가 아닌 다른 구분자에 의해 문자열을 구분하고 싶다면 split 함수 괄호 안에 구분하고 싶은 구분자를 써준다. 

 

마찬가지로 아주 간단한 예시를 들어보자.

s= "a.b.c.d.e.f.g"
div= s.split('.')

1번의 경우와 거의 동일하지만 이번에는 구분자가 띄어쓰기가 아닌 '.' 인것만 다르다. 이럴 경우에 split 함수 괄호 안에 ','를 써서 구분할 수 있다.

 

명시적으로 구분자가 '.' 다 라고 말하는 방법도 있다.

 

바로 split (sep ='.')를 사용하는 것이다. 

s= "a.b.c.d.e.f.g"
div= s.split(sep= '.')

이는 위와 같은 문법이지만 조금 더 명시적으로 나는 점에 의해 구분하고 싶다라는 의미의 가독성을 높여준다.

 


3. 문자열.split(sep= '구분자', maxsplit = 숫자) => 모든 구분자로 split 하지 않고 나누는 최대 횟수를 제한하고 싶을 때 

 

문자열을 구분하고는 싶으나 나누는 최대 횟수를 제한하고 싶을 때가 있다.

예를 들어 aa.bb.cc.dd.ee라는 문자열이 있는데 aa / bb/ cc/ dd.ee 이렇게 나누고 싶을 수 있다. 즉 모든 구분자 .에 의해 split하는 것이 아니라, 나누는 최대 횟수를 제한하는 경우이다. 

 

이런 경우는 2번처럼 구분자 parameter 뒤에 maxsplit parameter를 하나 더 넣어주면 된다. 

s= "a.b.c.d.e.f.g"
div= s.split(sep= '.', maxsplit=3)

이런식으로 써 주면 div에는 a / b/ c / d.e.f.g 로 나누어진 숫자 4짜리 배열이 만들어 질 것이다.


이런식으로 오늘은 python split 함수에 대하여 알아보았다. 

간단해보이지만 다양한 문법이 숨어있고, 유용한 경우가 많으니 잘 활용하길 바란다. 

 

python은 이러한 함수 덕분에 string을 처리하기 유리한 언어라고도 알려져있다. 


8월 30, 2023

검색 기본 설정 google로 바꾸기

가끔 Chrome google에서 검색을 하더라도 검색 결과를 입력하여 검색을 하면 yahoo나 bing으로 넘어갈 때가 있다.



분명히 나는 google로 검색했는데도 말이다! 

 

이럴 때 간단하게 해결할 수 있는 방법을 알아보도록 하겠다. 1분도 채 걸리지 않으니 같은 문제를 겪고 있는 사람이라면 시도해보기를 바란다. 



먼저 위와 같이 탭 상단에 있는 : 모양을 눌러 설정을 클릭해주자! 

 

그러면 Chrome 세팅 창으로 이동하게 될 것인데, 



사이드바의 '검색엔진'을 클릭해보자. 



만약 검색결과 창이 google이 아니라 다른 사이트로 redirect 되었던 사람이라면 검색엔진이 bing이나 보안 검색으로 설정되어 있을 가능성이 높다. 이를 google로 바꾸어 주면 해결된다. 



8월 30, 2023

html 자동정렬방법 (vscode extension: prettier 추천)

Java, C와 다르게 특히나 html 웹개발을 할때 항상 나는 줄맞춤에 어려움을 겪곤 했다. 예쁘게 코드를 적으려고 해도 언제부터인가는 tag의 줄맞춤이 엉망이 되곤 했기 때문이다. 

html을 사용해본 사람들은 많이 알겠지만 div tag를 open 했으면 close tag가 있어야 하는데 이런 것을 확인할 때 줄맞춤이 잘 되어 있지 않으면 이를 확인하는 데만 오랜 시간이 걸린다. 

 

이를 쉽게 해결하고 자동 줄맞춤을 하기 위해 나는 vscode의 prettier를 애용했다. vscode를 사용하면 다양한 extension이 있는데 이를 잘 알고 활용하는 것이 개발의 효율성을 높일 수 있는 방법인 것 같다. 우선 extension은 왼쪽 사이드 메뉴바에 있다.



빨간색으로 보이는 부분을 누르면 여러 extension을 검색해서 install할 수 있다. 

검색 부분에 아래와 같이 prettier라고 검색하고,



Prettier- Code formatter라고 나오는 것을 클릭하여 install 해주면 된다.



나는 이미 install이 되어 있기 때문에 install 대신 uninstall 버튼이 뜨는데, 처음 설치하는 사람들은 아마 install 버튼이 뜰 것이다. 이를 클릭만 해주면 쉽게 설치가 된다. 

 

그런데 prettier만 깔았다고 해서 html 모든 코드가 자동 정렬이 되는 것은 아니다. 나도 처음에 설치하고 왜 바로 정렬이 안되지? 라는 생각을 했던 것 같다. 

 

html 코드에 control + S를 눌러주면 자동 정렬이 된다. 또는 저장 버튼을 눌러주어도 된다. 주기적으로 control과 S를 함께 눌러주면 코드가 자동으로 정렬이 되면서 마음도 편해진다. 

 

웹개발 하는 사람들은 특히 참고하면 좋을 것 같다! 이것에 익숙해지면 C나 Java도 정렬하고 싶어져서 control S를 계속 누르게 되는데 아쉽게도 prettier가 적용되지 않는 코드들이다. 하지만 HTML에서는 매우 유용하니 적극 추천하는 extension이다!