분류 전체보기
-
MyBatis (3) SQL Mapper와 Bean 간의 property 전달2012.11.09
-
MyBatis (2) CRUD 예제 만들기2012.11.09
-
MyBatis (1) MyBatis 설정하기2012.11.09
-
(1) 스트럿츠1 정리 노트2012.09.18
-
Self-Study: Filter2012.08.17
MyBatis (3) SQL Mapper와 Bean 간의 property 전달
목차.
- MyBatis 기본 설정
- CRUD 예제 만들기
- SQL mapper 파라메터 전달
MyBatis는 SQL Mapping XML 구문을 Java 코드로 변환시 디폴트로 PreparedStatement 구문으로 바꾸고 전달되는 파라메터 들을 PreparedStatement 구문의 파라메터 '?'로 대응시킨다.
<select id="user" parameterType="int" resultType="User"> SELECT * FROM user WHERE id = #{id} </select>
'#{id}'는 int 타입의 파라메터이며 결과는 User 객체에 저장된다. 이 SQL Mapping XML 구문은 다음과 같은 내용의 Java 구문으로 변환된다.
String user = "SELECT * FROM user WHERE id = ?"; PreparedStatement ps = conn.preparedStatement(user); ps.setInt(1, id);
빈과 SQL 매퍼간의 파라메터 전달시 MyBatis는 Java 원시타입(Primitive type)의 경우 프로퍼티 getter/setter를 가지지 않고 파라메터 전체가 값을 대신한다.
만일 여러 개의 파라메터를 전달해야 할 경우는 2 가지 방법 중 선택하여 사용할 수 있다. Map을 사용하는 방법과 Mapper Interface Class에 @Param 어노테이션을 사용하는 방법이 있다.
Map을 사용
SQL Mapping XML
<insert id="user" parameterType="map" resultType="User"> INSERT INTO user (username, password) VALUES (#{username}, #{password}) </insert>
Mapper Interface Class
public int addParamMap(Map<string, string> map);
Running Java Code
... Map<string, string> param = new HashMap<string, string>(); param.put("username", "user1"); param.put("password", "1234"); mapper.addParamMap(param);
@Param 어노테이션을 사용
SQL Mapping XML
<intert id="user"> INSERT INTO user (username, password) VALUES (#{username}, #{password}) </insert>
Mapper Interface Class
public int addParamAnnotation( @Param("username") String username, @Param("password") String password);
Running Java Code
mapper.addParamAnnotation("user1", "1234");
MyBatis (2) CRUD 예제 만들기
목차.
- MyBatis 기본 설정
- CRUD 예제 만들기
- SQL mapper 파라메터 전달
SQL Mapper
UserMapper.xml
<mapper namespace="exercise.mybatis3.persistence.UserMapper"> <insert id="add" parameterType="User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user (username, password, level, reg_date) VALUES (#{username}, #{password}, #{level}, #{regDate}) </insert> <select id="get" parameterType="int" resultType="User"> SELECT id, username, password, level, reg_date AS 'regDate' FROM user WHERE id = #{id} </select> <select id="getAll" resultType="User"> SELECT id, username, password, level, reg_date AS 'regDate' FROM user </select> <delete id="delete" parameterType="int"> DELETE FROM user WHERE id = #{id} </delete> <delete id="deleteAll"> DELETE FROM user </delete> <select id="count" resultType="int"> SELECT COUNT(*) FROM user </select> <select id="lastId" resultType="int"> SELECT id FROM user ORDER BY id DESC LIMIT 1 </select> </mapper>
Mapper Interface Class
UserMapper.java
public interface UserMapper { public void add(User user); public User get(int id); public List<user> getAll(); public void delete(int id); public void deleteAll(); public int count(); public int lastId(); }
Domain Object
User.java
public class User implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String username; private String password; private Integer level; private String regDate; public User() { // TODO Auto-generated constructor stub } public User(String username, String password, Integer level, String regDate) { this.username = username; this.password = password; this.level = level; this.regDate = regDate; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getLevel() { return level; } public void setLevel(Integer level) { this.level = level; } public String getRegDate() { return regDate; } public void setRegDate(String regDate) { this.regDate = regDate; } @Override public boolean equals(Object comp) { User target = (User)comp; if (this.id.equals(target.id) && this.username.equals(target.username) && this.password.equals(target.password) && this.level.equals(target.level) && this.regDate.equals(target.regDate)) return true; else return false; } }
Unit test
TestUserMapper.java
public class TestUserMapper { static SqlSessionFactory sf; List<user> users; @BeforeClass public static void setUpBeforeClass() throws Exception { String resource = "exercise/mybatis3/persistence/mybatis-config.xml"; Reader reader = Resources.getResourceAsReader(resource); sf = new SqlSessionFactoryBuilder().build(reader, "testing"); } @Before public void setUp() { users = Arrays.asList( new User("user1", "1234", 1, "2012-11-09"), new User("user2", "1234", 1, "2012-11-09"), new User("user3", "1234", 1, "2012-11-09") ); } @Test public void testAdd() { SqlSession session = sf.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); mapper.deleteAll(); mapper.add(users.get(0)); assertThat(1, is(mapper.count())); } finally { session.close(); } } @Test public void testGet() { SqlSession session = sf.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); mapper.deleteAll(); mapper.add(users.get(0)); User user = mapper.get(mapper.lastId()); assertTrue((users.get(0)).equals(user)); } finally { session.close(); } } @Test public void testGetAll() { SqlSession session = sf.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); mapper.deleteAll(); for (User user : users) { mapper.add(user); } assertThat(users.size(), is(mapper.count())); } finally { session.close(); } } @Test public void testDelete() { SqlSession session = sf.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); mapper.deleteAll(); for (User user : users) { mapper.add(user); } mapper.delete(mapper.lastId()); assertThat(users.size() - 1, is(mapper.count())); } finally { session.close(); } } } }
MyBatis (1) MyBatis 설정하기
목차.
- MyBatis 기본 설정
- CRUD 예제 만들기
- SQL mapper 파라메터 전달
MyBatis는 iBATIS의 새로운 버전으로 국내에서 가장 많이 사용되는 ORM Framework 중 하나이다. MyBatis는 문서화가 잘 되어 있다고 하지만 막상 실전에 적용시키기에는 이전 버전인 iBATIS에 비해 예제가 부족한 듯하여 자료 정리겸 레퍼런스를 만들어 볼까 한다. 1
우선 MyBatis를 설정하고 간단한 예제를 만들어 본 다음, Spring 3로 Bean을 등록하는 예제로 확장시켜 볼 예정이다.
본 예제는 다음과 같은 개발환경하에서 작성되었다.
- Eclipse Java EE IDE (Juno)
- PropertiesEditor (by Sou Miyazaki)
- Java SE 6
- MyBatis-3.1.1
- JUnit 4
1. MySQL Table
CREATE TABLE user ( id INT(5) NOT NULL PRIMARY KEY AUTO_INCREMENT, username VARCHAR(16) NOT NULL, password VARCHAR(16) NOT NULL, level INT(2) NOT NULL DEFAULT '0', reg_date DATE NOT NULL );
2. Configuration
development.properties
url=jdbc:mysql://localhost/development?characterEncoding=utf-8 driver=com.mysql.jdbc.Driver user=development pass=test123
mysql-config.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="exercise/mybatis3/persistence/development.properties"/> <settings> <setting name="defaultExecutorType" value="REUSE"/> <setting name="useGeneratedKeys" value="true"/> </settings> <typeAliases> <!-- Type Aliases List --> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="JNDI"> <property name="initial_context" value="java:comp/env" /> <property name="data_source" value="jdbc/insure"/> </dataSource> </environment> <environment id="testing"> <transactionManager type="MANAGED"> <property name="closeConnection" value="false"/> </transactionManager> <dataSource type="POOLED"> <property name="driver" value="${driver}" /> <property name="url" value="${url}" /> <property name="username" value="${user}" /> <property name="password" value="${pass}" /> </dataSource> </environment> </environments> <mappers> <!-- Mapper List --> </mappers> </configuration>
3. SQL Mapper (CRUD)
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="exercise.mybatis3.persistence.UserMapper"> <insert id="add" parameterType="User" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user (username, password, level, reg_date) VALUES (#{username}, #{password}, 1, NOW()) </insert> <select id="count" resultType="int"> SELECT COUNT(*) FROM user </select> </mapper>
UserMapper.java
package exercise.mybatis3.persistence; import exercise.mybatis3.domain.User; public interface UserMapper { public void add(User user); public int count(); }
4. Domain Object
User.java
package exercise.mybatis3.domain; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L; private Integer id; private String username; private String password; private Integer level; private String regDate; public User() { // TODO Auto-generated constructor stub } public User(String username, String password) { this.username = username; this.password = password; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Integer getLevel() { return level; } public void setLevel(Integer level) { this.level = level; } public String getRegDate() { return regDate; } public void setRegDate(String regDate) { this.regDate = regDate; } }
5. Unit Test (CRUD)
TestUserMapper.java
package exercise.mybatis3.test; import static org.junit.Assert.assertThat; import static org.hamcrest.CoreMatchers.is; import java.io.Reader; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import exercise.mybatis3.domain.User; import exercise.mybatis3.persistence.UserMapper; public class TestUserMapper { static SqlSessionFactory sf; User user; @BeforeClass public static void setUpBeforeClass() throws Exception { String resource = "exercise/mybatis3/persistence/mybatis-config.xml"; Reader reader = Resources.getResourceAsReader(resource); sf = new SqlSessionFactoryBuilder().build(reader, "testing"); } @Before public void setUp() { user = new User("user1", "1234"); } @Test public void testAdd() { SqlSession session = sf.openSession(); try { UserMapper mapper = session.getMapper(UserMapper.class); mapper.add(user); assertThat(1, is(mapper.count())); } finally { session.close(); } } }
- 데이터베이스와 객체와의 관계를 맵핑시켜 퍼시스턴스 로직처리를 도와주는 프레임워크 (Object Relational Mapping) [본문으로]
(1) 스트럿츠1 정리 노트
소개
스트럿츠는 자바 서블릿, 자바 빈, 리소스 번들, XML 뿐만 아니라 BeanUtil이나 Chain of responsibility(책임의 연쇄)와 같은 다양한 아파치 커먼즈 팩키지와 같은 표준 기술을 기반으로한 유연한 컨트롤 계층이다. 프레임워크는 표준적인 배포 방법과 검증된 디자인 패턴에 기반하여 응용프로그램에 대한 확장된 개발환경을 만들어 낼 수 있다.
프레임워크는 자신만의 웹 컨트롤러 구성 요소를 제공하고 모델과 뷰를 위한 다른 기술을 통합한다. 모델을 위한 프레임워크는 JDBC와 EJB와 같은 표준 데이터 접근 기술 뿐만 아니라 대부분의 서드파티 패키지들인 Hibernate, iBATIS, 객체관계형 브리지와 상호 작용할 수 있다. 뷰를 위한 프레임워크는 JSTL과 JSF 뿐만 아니라 벨로시티 템플리트, XSLT와 기타 다른 프리젠테이션 시스템을 포함한 Java Server Page와 잘 작동한다.
프레임워크의 컨트롤러는 어플리케이션의 모델과 웹 뷰 사이에서 다리 역할을 한다. 요청이 도착하면 컨트롤러는 액션 클래스를 호출한다. 액션 클래스는 어플리케이션의 상태를 검사하거나 갱신하기 위해 모델과 상의한다. 프레임워크는 모델과 뷰 사이에 데이터를 전송할 수 있도록 ActionForm 클래스를 제공한다.
대부분 모델은 자바 빈의 집합으로 구성된다. 일반적으로 개발자는 ActionForm과 모델 객체(혹은 퍼사드) 사이의 데이터 전송을 위해 BeanUtils 커먼즈 툴을 사용한다. 바람직한 것은 모델은 "heavy lifting"을 할 것이고, Action은 "교통 경찰" 역할이나 어댑터 역할을 한다.
Model : 시스템 상태와 비즈니스 로직 자바 빈즈
MVC 기반 시스템에서 모델은 두 가지의 주요 부분 – 시스템 내부 상태와 그 상태를 변경할 수 있는 작업 – 으로 나눌 수 있다.
마치 명사와 동사에 비유할 수 있을 것이다.
많은 경우 어플리케이션은 하나 이상의 자바 빈즈의 집합으로 시스템 내부 상태를 나타낸다. 빈 속성은 시스템의 세부 상태 정보를 나타낸다. 어플리케이션의 복잡한 정도에 따라 이 빈들은 자신의 상태나 자신의 상태를 유지할 수 있는 방법을 포함하거나, 다른 구성 요소에서 시스템의 상태를 검색하는 방법을 알 수 있다. 이 구성 요소는 데이터베이스, 검색엔진, 엔티티 엔터프라이즈 자바 빈즈, LDAP 서버나 완전히 다른 것이 될 수 있다.
대규모 응용프로그램은 종종 빈이나 시스템 정보를 유지하는 빈을 호출 할 수 있는 메서드와 같은 비즈니스 명령 집합으로 표현된다. 이 빈은 또한 사용자의 신용카드를 승인하고 입고나 출고하는 창고로 주문을 보내는 checkOut() 메서드일 수 있다. 다른 시스템은 세션 엔터프라이즈 자바 빈즈로 별도로 사용할 수 있는 명령을 나타낸다.
반면에 소규모 응용프로그램에서는 프레임워크의 제어 계층의 일부인 Action 클래스들에 사용가능한 명령들을 포함할 수 있다. 이 것은 로직이 매우 간단하거나 비즈니스 로직의 재사용이 필요없는 경우 유용할 수 있다.
프레임워크 아키텍쳐는 다양하게 모델에 접근할 수 있도록 충분히 유연하지만 무엇을 할 것인지에 관련된 Action 클래스와 어떻게 할 것인지와 관련있는 비즈니스 로직을 분리할 것을 강력 추천한다.
View : JSP 페이지들과 표현 구성 요소(Presentation Components)들
스트럿츠 기반의 응용프로그램의 뷰는 종종 Java Server Page(JSP) 기술을 사용하여 구성된다. JSP 페이지는 “템플릿 텍스트”로 불리는 정적 HTML(혹은 XML)로 구성되고, 페이지 요청시 해석되는 특별한 액션 태그 기반의 동적 컨텐츠를 추가할 수 있는 능력을 더한다. JSP 환경은 Java Server Page 명세에 설명된 <jsp:useBean>과 같은 표준 액션 태그를 포함한다. 내장된 액션에 추가로 여러분 자신의 태그를 정의하여 커스텀 태그 라이브러리안에 구성한다.
프레임워크는 ActionForm 빈즈를 통해 완벽하게 국제화되고 우와하게 상호작용하는 사용자 인터페이스를 만들어 내는 커스텀 태그 라이브러리의 집합을 포함한다. ActionForm은 응용프로그램이 요청한 입력을 읽고 유효성 검사를 한다.
Controller : ActionServlet과 ActionMapping
스트럿츠는 응용프로그램의 컨트롤러 부분을 제공한다. 컨트롤러는 클라이언트(일반적으로 웹브라우저)의 요청을 받아서 수행될 비즈니스 로직을 결정하고 사용자 인터페이스의 다음 단계를 적절한 뷰 구성요소에게 만들어 내는 책임을 위임한다. 프레임워크에서 컨트롤러의 주요 구성요소는 클래스 ActionServlet의 서블릿이다. 이 서블릿은 ActionMapping의 집합을 정의하여 구성한다. ActionMapping은 입력된 요청의 URI에 대해 일치하는 Action 클래스 이름을 지정하는 경로를 정의한다. 모든 Action은 org.apache.struts.action.Action의 하위 클래스이다. Action은 비즈니스 로직 클래스의 호출을 캡슐화하고 결과를 해석하여 응답을 생성할 적절한 뷰 구성 요소의 디스패치를 제어한다. 프레임워크가 뷰를 디스패치하는 동안 실제 렌더링되는 뷰는 스코프를 벗어난다.
프레임워크는 또한 컨트롤러를 작동하는 데 필요한 표준들 이외에 추가 속성들을 갖는 ActionMapping 클래스를 이용할 수 있는 기능을 지원한다. 이렇게 하면 응용프로그램에 대한 추가적인 정보를 저장하면서 여전히 프레임워크의 나머지 기능을 사용할 수 있다. 또한, 프레임워크는 논리적인 “이름”을 이용하여 예를 들면 메인 메뉴 페이지에서 액션 메서드에 대응하는 JSP 페이지를 위치 지정없이 전달할 수 있도록 제어할 수 있다. 이러한 특징은 컨트롤 로직(수단)과 뷰 로직(표현)를 분리하는데 도움을 준다.
Self-Study: Filter
필터(Filter)는 요청(Request)을 서블릿에 앞서 중간에서 가로채서 처리한 후 서블릿으로 넘겨주어 서블릿의 수정없이 필요한 처리를 할 수 있다.
필터 적용 대상들
Request 필터:
- 보안 체크
- 요청 헤더와 바디 포맷팅 수정
- 요청 감시와 로그 처리
Response 필터
- 응답 스트림 압축
- 응답 스트림 내용 추가 혹은 수정
- 새로운 응답 스트림 생성
필터의 설정
체인(Chain)식으로 연결하여 사용이 가능하다. 필터는 하나의 완전한 컴포넌트로써 필터간 전처리가 필요없다. 실행 순서를 지정할 수 있다.
필터는 서블릿과 비슷하다.
컨테이너에 선언하고 컨테이너에 의해 호출된다.
컨테이너가 생명주기를 관리한다.
배포기술자(DD)에 설정한다.
필터의 생명주기
- init() 메서드 - 컨테이너가 필터를 인스턴스화할 때 호출하므로 필터의 호출 전에 전처리로 설정할 내용을 기술. FilterConfig 객체 참조를 나중에 쓰기 위하여 지역변수에 저장하는 코드 작성.
- doFilter() 메서드 - 컨테이너가 현재 요청에 필터를 적용할 경우 호출. 다음 3개의 인자 사용.
- ServletRequest 객체
- ServletResponse 객체
- FilterChain 객체 - 필터 간의 처리순서를 지정하는 용도. 배포기술자에 지정한 다음 처리 순서의 필터 참조 저장.
- destory() 메서드 - 컨터이너가 필터 인스턴스를 제거할 때 호출. 필터 인스턴스 제거전 후처리할 내용 기술.
public class ExampleFilter implements Filter { private FilterConfig fg; /** * 필수 구현 메서드로 일반적으로 * FilterConfig 객체 참조를 로컬에 저장하는 용도 */ public void init(FilterConfig config) throws ServletException { this.fc = config; } public void doFilter( ServletRequst requset, ServletResponse response, FilterChain chain // 다음 실행해야할 필터 참조 ) throws ServletException, IOException { ... 처리할 내용 ... /* 다음 실행될 필터나 서블릿을 호출. */ chain.doFilter(request, response); } public void destory() { ... 후처리 내용 ... } }
스택에 쌓이는 필터들
다 수의 필터들을 순서대로 처리하기 위해 컨테이너 내에 스택을 두어 처리한다.
필터의 선언 및 실행 순서
배포기술자(DD)에 선언된 매핑 순서를 따른다.
필터선언명 필터클래스 패키지 경로 변수명 값 필터선언명 URL 패턴이나 필터 실행경로 필터선언명 서블릿 선언명
※ 컨테이너가 필터 실행 순서를 정하는 규칙
- URL 패턴으로 적용되는 필터가 최우선 순위로 체인에 등록된다. 배포기술자에 정의된 순서를 따른다.
- URL 패턴으로 일치하는 필터들이 체인에 순서대로 등록되고 난 다음 <servlet-name>으로 일치하는 필터를 찾아 정의된 순서대로 체인에 등록한다.
요청디스패처로 들어오는 요청에 필터 적용(Servlet 2.4 이상)
필터선언명 URL 패턴이나 필터 실행경로 REQUEST AND/ORINCLUDE AND/ORFORWARD AND/ORERROR