JAVA 웹개발 과정 국비 79일차 정리

79일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. Spring Framework 이어서..

1) bean 태그로 인스턴스 생성하는 이유

이전 시간에 스프링에서 인스턴스를 생성하는 두가지 방법을 배웠다.

  1. xml에 bean코드로 인스턴스 생성
  2. 어노테이션으로 인스턴스 생성

여기서 든 의문점.
어노테이션 방법이 더 쉽고 간편한데 왜 xml 방식도 써야하는 걸까?
직접 만든 클래스가 아닌 경우에는 어노테이션을 쓸 수 없기 때문이다.
대표적인 예가 바로 아래의 DBCP 코드이다.
따라서 두가지 방식을 적절히 혼용해야 한다.

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
	<property name="username" value="kh"/>
	<property name="password" value="kh"/>
	<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
	<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"/>
	<property name="initialSize" value="30"/>
</bean>	

2) IOC 환경

이렇게 개발자가 인스턴스를 관리하는 것이 아닌, 스프링이 인스턴스를 관리하고 제어하게 되면서 이런 환경을 IOC 환경이라고 한다.
Inversion Of Control의 약자이며, 제어의 역전이라고 부른다.
스프링 컨테이너를 IOC 컨테이너라고 부르기도 한다.

3) DL 이란

Dependency Lookup 의 줄임말이다.
예전에 배웠던 JNDI처럼 lookup(참조)해서 인스턴스를 꺼내오는 방법이다.
스프링 프로젝트에서는 정말 특수한 경우가 아니면 잘 사용하지 않는다고 한다.
실시간 채팅 프로그램을 구현할 때 필요한 개념이라고해서 좀 찾아봤는데, 정보가 나오질 않네..
정리가 다 끝나고 채팅 프로그램을 구현해보며 공부를 더 해봐야겠다.

12/29일 추가

DL의 개념이 잘 이해가 가지 않아 추가 질문을 했다.
웹 어플리케이션에 DL이 잘 쓰이지 않는 이유는 톰캣이 켜지고, 톰캣이 스프링을 만들고, 그 스프링이 DI를 실행하기 때문에, 스프링이 자동으로 다 만들어주기 때문이다.
그러나 네이티브 앱을 개발할 때는 스프링을 직접 만들어줘야한다. 그래서 스프링 인스턴스 풀 안에 있는 인스턴스를 쓰려면 스프링에게 찾아달라고 해야한다.
즉 정리하자면, DL은 자동적으로 인스턴스가 만들어지는 셋팅이 되어있지 않을 때 인스턴스 풀 안에 있는게 필요할 때 사용한다.
아래와 같이 getBean을 사용하는 것이 DL 방법이다.
자세한 샘플코드는 ctx.getBean을 구글링하면 많이 나오니 참조..

// 스프링컨테이너야 니가 가진 인스턴스 중에 이런 자료형을 가진 인스턴스를 찾아서 가져다 줘.
ctx.getBean(ContactDAO.class);

4) Maven의 의존성 해결

가져오는 라이브러리가 다른 라이브러리에 의존성이 있게 되면 추가적인 라이브러리를 가져다가 또 추가해야하는 번거로움이 있다.
즉, Maven으로 라이브러리 관리를 하지 않는다면 의존성이 있는 라이브러리를 추가할 때 연쇄적으로 계속 라이브러리를 가져와야하는 것이다.
그러나 Maven으로 라이브러리 관리를 한다면, 특정 라이브러리를 가져올 때, 자동으로 의존성이 있는 라이브러리들까지 가져오기 때문에 더욱 편리하다.
어제 수업에서 DBCP를 추가할 때, logging 라이브러리와 poo12 라이브러리를 추가한 적이 없지만, 같이 추가되었던 것을 생각해보면 더 쉽게 이해할 수 있다.

5) xml 안의 properties

xml 파일을 잘 보면 properties 태그를 볼 수 있다.
properties는 바로 변수라는 개념과 동일하다.
아래 코드를 보면, springframework-version인 5.3.9를 변수처럼 el로 가져다가 쓰는 것을 볼 수 있다.
스프링의 버전이 바뀔 때 번거롭게 다 고치지 않아도 자동으로 바뀌게 만들어준다.

<properties>
	<java-version>11</java-version>
	<org.springframework-version>5.3.9</org.springframework-version>
	<org.aspectj-version>1.6.10</org.aspectj-version>
	<org.slf4j-version>1.6.6</org.slf4j-version>
</properties>
<dependencies>
	<!-- Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${org.springframework-version}</version>

6) Spring JDBC

기존에 쓰던 JDBC 말고, Spring JDBC를 쓰면 조금 더 편하게 코드를 작성할 수 있다.

① 라이브러리 추가

Maven Repository에서 Spring JDBC라고 검색하여 적절한 버전을 가져온다.
실습으로는 스프링에 맞춰 5.3.9버전으로 가져왔다.
아래 쪽 5.3.9 변수를 5번처럼 변수로 고쳐주면, 추후에 편하니 고쳐놓자.

② 인스턴스 생성

인스턴스 생성 코드부터 다르기때문에 root-context.xml에 새로 코드를 작성해준다.
스프링 JDBC를 쓴다고 DBCP를 쓰지 않는 것은 아니기 때문에 jdbcTemplate 안에 DBCP를 넣어준다.
property는 setter로 동작하므로 name값을 주고, ref로 DBCP의 id값을 전달한다.

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource"/>
</bean>

ref라는 단어가 조금 생소할 수도 있는데, xml에서 setter를 사용할 때 int, double, char, String은 ref 대신 value라고 명세를 하는 반면 그 외에 참조자료형들은 ref라고 쓰는 것이다.
즉 단어만 다르지 개념은 비슷하다.

이때, jdbcTemplate가 dataSource를 자신의 setter로 받는다.
이것도 스프링의 설정에 의해서 인스턴스를 주입시키는 것이기 때문에 이 과정도 Dependency Injection의 한 부분으로 취급이 된다.

③ int 형을 리턴할 때

insert, update, delete와 같이 결과값을 int형으로 받을 때는 간단하다.
update 메소드를 사용한다.
첫 인자값에 sql문을 넣어주고, 물음표는 가변인자로 받기 때문에 맞춰서 적어준다.
commit과 close도 알아서 자동으로 한다.

public int insert(ContactDTO dto) throws Exception {
		
		String sql="INSERT INTO contact VALUES(contact_seq.nextval,?,?)";
		return jdbc.update(sql, dto.getName(), dto.getContact());
	}
	
public int delete(int seq) throws Exception {
		
		String sql = "DELETE FROM contact WHERE seq = ?";
		return jdbc.update(sql, seq);
	}

public int update(ContactDTO dto) throws Exception {
		String sql = "UPDATE contact SET name=?,contact=? WHERE seq = ?";
		return jdbc.update(sql, dto.getName(), dto.getContact(), dto.getSeq());
	}

④ List를 리턴할 때

꺼내와야하는 데이터가 1개가 아닌 여러개라면 query라는 메소드를 사용한다.
데이터베이스에서 꺼내오는 값을 어떻게 써먹을지를 스프링JDBC가 정해줄 수 없기 때문에 개발자가 정해야 한다.
query 메소드는 첫번째 인자값으로 sql, 두번째 인자값으로 RowMapper 인스턴스를 갖는다.
그러나 RowMapper는 인터페이스이기 때문에 인스턴스 생성을 할 수 없다.
query 메소드를 완성시키기 위해서는 RowMapper를 전달해야하는데 인스턴스 생성을 할 수 없다??
인스턴스를 생성할 수 없는 이유는 추상메소드를 가지기 때문이므로, 추상메소드를 구현해주면 인스턴스 생성이 가능하다.
뒤에 바로 중괄호를 열고 ctrl+space를 누르면 추상메소드를 바로 오버라이딩 할 수 있게 된다.
이 방법을 익명클래스 기법이라고 한다.

public List<ContactDTO> selectAll() throws Exception{
	String sql = "SELECT * FROM contact";
	return jdbc.query(sql, new RowMapper() {
		@Override
		public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
			return null;
		}
	});
}

인스턴스 생성이 불가한 인터페이스를 query가 인자값으로 받으려고 하는 것이 이상할 수도 있지만, 이 메소드를 만든 사람이 의도한 것은 이 query 메소드를 사용하는 사람이 mapRow메소드의 내용을 채우도록한 것이다.
즉, 메소드가 다른 메소드를 인자로 받으려고 하는 기법이고,이것을 자바스크립트에서는 콜백패턴이라고 한다.
자바에서는 함수를 값으로 넘길 수 없기 때문에, 함수를 구현한 인스턴스를 인자로 넘길 수 있게 만든다.

마지막으로 원하는 값들을 dto에 넣어주고 리턴시켜주면, 리턴되는 행 하나하나마다 스프링 JDBC가 매핑을 시켜준다.

@Override
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
	return null;
}

mapRow라는 메소드를 왜 구현하게 만든 것일까?
데이터 베이스에서 꺼낸 resultset을 어떤 타입으로 만들 것인지를 알려주기를 원하기 때문이다.
따라서 Object로 리턴타입을 만들어 놓은 것인데, Object는 사용하기 까다로운 부분이 있으므로 RowMapper가 제너릭을 가진다는 특성을 이용해 원하는 자료형으로 바꿔주고 다시 Ctrl+Enter를 해주면 원하는 자료형의 mapRow 메소드가 다시 생성된다.

public List<ContactDTO> selectAll() throws Exception{
	String sql = "SELECT * FROM contact";
	return jdbc.query(sql, new RowMapper<ContactDTO>() {
		@Override
		public ContactDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
			ContactDTO dto = new ContactDTO();
			dto.setSeq(rs.getInt("seq"));
			dto.setName(rs.getString("name"));
			dto.setContact(rs.getString("contact"));
			return dto;
		}
	});
}

⑤ select로 1개의 int 데이터를 리턴할 때

레코드의 총 갯수 같이 하나의 데이터를 int형으로 받아올 때는 queryForObject메소드를 사용한다.
첫번째 인자로 sql문을, 두번째 인자로는 어떤 데이터 타입을 리턴받을 건지를 명세해준다.

public int selectCount() throws Exception {
	String sql = "SELECT count(*) FROM contact";
	return jdbc.queryForObject(sql, Integer.class);
}

⑥ select로 1개의 dto 데이터를 리턴할 때

5번과 동일하게 한개의 데이터를 꺼내기 때문에 queryForObject 메소드를 쓴다. 그러나 ResultSet을 사용하므로 rowMapper를 사용해야한다.
queryForObject의 첫번째 인자로 sql문, 두번째 인자로 익명클래스를 만들고, 세번째 인자값으로 가변인자를 가진다. 아래는 물음표가 하나이기때문에 seq 1개를 넣어준다.

public ContactDTO search(int seq) throws Exception {
	
	String sql = "SELECT * FROM contact WHERE seq = ?";
	return jdbc.queryForObject(sql, new RowMapper<ContactDTO>() {
		@Override
		public ContactDTO mapRow(ResultSet rs, int rowNum) throws SQLException {
			ContactDTO dto = new ContactDTO();
			dto.setSeq(rs.getInt("seq"));
			dto.setName(rs.getString("name"));
			dto.setContact(rs.getString("contact"));
			return dto;
		}
	}, seq);
}

7) resources 폴더

스프링에서 image, css, js 등의 파일들을 자원(resource)이라고 부른다.
mvc2에서는 이 자원들의 위치를 마음대로 정했지만, 스프링에서는 webapp 폴더 안에 resources 폴더에 보관하는 것이 편하다.
그 이유는 servlet-context.xml안에 있는 아래 코드 때문이다.
img태그는 request 를 자동으로 발생시키기 때문에 dispatcher로 전달되어야한다. 그러면 dispatcher는 handlermapping에게 물어보고 없다면 404에러를 발생시킬 것이다.
경로를 제대로 적어주었다는 가정하에 handlermapping에 등록시킨 적도 없는데 이러한 에러가 나지 않는 이유는 아래 코드로 인해 request가 dispatcher에게 가지 않고 location으로 바로 갈 수 있게 만들어주기 때문이다.

<resources mapping="/resources/**" location="/resources/" />

기존 코드는 위와 같은데, 아래와 같이 적절히 바꿔주면 자원들을 효율적으로 접근할 수 있게 만들 수 있다.

<resources mapping="/images/**" location="/resources/images/" />
<resources mapping="/css/**" location="/resources/css/" />

8) method속성

컨트롤러에 @RequestMapping 속성 중 method가 있다.

@Controller
public class HomeController {

	@RequestMapping(value = "/", method = RequestMethod.GET)
	public String home(Locale locale, Model model) {
		return "home";
	}
}

만약 위처럼 method에 GET을 지정해버리면 / 을 통해 들어올때 post 방식 request는 막아버린다.
반대도 똑같이 동작한다.
method 속성을 아예 빼버린다면, get방식과 post방식 둘 다 허용한다.
이렇게 하는 이유는 많지 않지만 보안 이슈 때문이다.
img 태그의 src를 이용한 CSRF 공격은 GET 방식으로 이루어지므로 POST 방식만 허용해놓는다면 완벽히는 아니지만 어느정도 공격을 막을 수 있다.
쓸 일은 없지만 알아두도록 하자~

댓글남기기