잊지 않겠습니다.

이제 구성된 서비스를 중심으로 REST API 서버를 구축해보도록 한다. 


먼저, 구축할 REST API는 다음과 같다. 


  • rest/person/{id} : (GET) , Person 데이터를 얻어온다.
  • rest/person/list : (GET) 전체 사용자 데이터를 얻어온다.
  • rest/person/edit : (POST) 사용자 데이터를 수정한다.
  • rest/person/delete/{id} : (DELETE) 사용자를 삭제한다.

1. pom.xml 설정


REST API를 구성하기위해서, 객체의 JSON serialization을 담당하는 jar가 필요하다. 주로 사용되는 jackson을 추가하도록한다.


<dependency>

<groupId>org.codehaus.jackson</groupId>

<artifactId>jackson-mapper-asl</artifactId>

<version>1.9.9</version>

</dependency>


주의사항 : jackson-core-asl은 jackson의 core만을 가지고 있다. jackson-mapper-asl을 추가해야지, spring과 연결되는 REST view가 구현된다. 


2. web.xml에 다음과 같은 설정을 추가한다.


<servlet>

<servlet-name>spring</servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

<init-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/spring-servlet.xml</param-value>

</init-param>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>spring</servlet-name>

<url-pattern>/</url-pattern>

</servlet-mapping>


servlet이름을 spring으로 하고, spring bean 설정 file이 위치한 path를 설정한다. 이때, servlet-class는 spring web framework의 Front Controller Class인 DispatcherServlet이 된다.



3. spring-servlet.xml을 작성한다.


controller들을 등록하기 위해서, 다음 설정을 추가한다.  아래 설정은 Spring MVC의 @Controller annotation을 구성한 모든 class들을 Spring bean에 등록하고, URL에 mapping하는 작업을 한다.


<context:component-scan base-package="com.xyzlast.controllers"></context:component-scan>

<mvc:annotation-driven />



4. PersonAPIController를 작성한다. 


@Controller annotation을 이용해서 Controller로 등록을 하고, @RequestMapping을 이용해서 URL을 구성한다.


@Controller

public class PersonAPIController {

@Resource(name="personService")

private IPersonService personService;

@RequestMapping(value = "rest/person/{id}")

@ResponseBody 

public Person get(@PathVariable Integer id) {

Person person = personService.get(id);

return person;

}

@RequestMapping(value="rest/person/list")

@ResponseBody

public List<Person> list() {

return personService.getAll();

}

@RequestMapping(value="rest/person/add", method=RequestMethod.PUT)

@ResponseBody

public Person add(@RequestParam(value="firstName", required=true) String firstName,

@RequestParam(value="lastName", required=true) String lastName, 

@RequestParam(value="money", required=true) Double money) {

return personService.add(firstName, lastName, money);

}

@RequestMapping(value="rest/person/edit", method=RequestMethod.POST)

@ResponseBody

public Person edit(@RequestParam(value="id", required=true) Integer id, 

          @RequestParam(value="firstName", required=true) String firstName,

          @RequestParam(value="lastName", required=true) String lastName,

          @RequestParam(value="money", required=true) Double money) {

return personService.edit(id, firstName, lastName, money);

}

@RequestMapping(value="rest/person/delete/{id}", method = RequestMethod.DELETE)

@ResponseBody

public Person delete(@PathVariable Integer id) {

Person person = personService.get(id);

if(person != null) {

personService.delete(id);

}

return person;

}

}




5. Controller Test code를 작성한다.

Test code는 MockHttpServletRequest와 MockHttpServletResponse를 이용해서 구성하면 된다. 각 URL에서의 request/response 모두를 측정가능하다. 


먼저, servlet-api를 pom.xml에 추가한다. tomcat과 같은 servlet container가 항시 로드하는 jar지만, 지금은 JUnit test runner에서 돌리기 위한 것임으로 maven에 추가를 해준다. 테스트에서만 사용할 것이기 때문에 scope를 test로 주는것이 매우 중요하다.


<dependency>

<groupId>org.apache.tomcat</groupId>

<artifactId>servlet-api</artifactId>

<version>6.0.35</version>

</dependency>


그리고 아래와 같이 테스트 코드를 작성한다.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/spring-context.xml")
public class PersonAPIControllerTest {
@Resource
private IPersonService personService;
    @Autowired
    private RequestMappingHandlerAdapter handlerAdapter;

    @Autowired
    private RequestMappingHandlerMapping handlerMapping;
    
    private Person person;
    
    @Before
    public void setup() {
    person = personService.add("ykyoon", "lastName", 100.00);
    }
    
    @Test
    public void get() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("GET","/rest/person/" + person.getId().toString());
    MockHttpServletResponse response = new MockHttpServletResponse();

    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertNull(modelAndView);
    }
    
    @Test
    public void edit() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("POST", "/rest/person/edit");
    request.addParameter("id", person.getId().toString());
    String editedFirstName = "EDITED_FIRSTNAME";
    request.addParameter("firstName", editedFirstName);
    request.addParameter("lastName", person.getLastName());
    request.addParameter("money", person.getMoney().toString());
   
    MockHttpServletResponse response = new MockHttpServletResponse();
   
    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertNull(modelAndView);
   
    Person editedPerson = personService.get(person.getId());
    assertThat(editedPerson.getFirstName(), is(editedFirstName));
    }
    
    @Test
    public void delete() throws Exception {
    MockHttpServletRequest request = new MockHttpServletRequest("DELETE", "/rest/person/delete/" + person.getId().toString());
    MockHttpServletResponse response = new MockHttpServletResponse();

    Object handler = handlerMapping.getHandler(request).getHandler();
    ModelAndView modelAndView = handlerAdapter.handle(request, response, handler);    
    System.out.println(response.getContentAsString());    
    assertThat(response.getStatus(), is(200));
    assertNull(modelAndView);

    Person deletedPerson = personService.get(person.getId());
    assertNull(deletedPerson);
    }
}






Posted by Y2K
,

구성된 project는 아주 기본적인 Hibernate를 이용한 ORM 구성을 가지게 된다. 

추가로, Transaction과 Connection Polling을 구성하도록 한다. 


1. Transaction의 추가


<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-tx</artifactId>

<version>3.1.2.RELEASE</version>

</dependency>


<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aop</artifactId>

<version>3.1.2.RELEASE</version>

</dependency>


spring trasaction을 위한 jar와 aop를 위한 jar를 추가한다.

생성된 PersonService의 interface를 선언한다. 

public interface IPersonService {
Person add(String firstName, String lastName, Double money);
List<Person> getAll();
Person get(Integer id);
Person edit(Integer id, String firstName, String lastName, Double money);
void delete(Integer id);
}

PersonService에 interface구현을 추가하고, @Transaction annotation을 붙인다. 
구현된 PersonService는 다음과 같다.

@Service("personService")
@Transactional
public class PersonService implements IPersonService {
@Resource
private SessionFactory sessionFactory;
@SuppressWarnings("unchecked")
public List<Person> getAll() {
Session session = sessionFactory.getCurrentSession();
List<Person> persons = (List<Person>) session.createCriteria(Person.class).list();
return persons;
}
public Person get(Integer id) {
Session session = sessionFactory.getCurrentSession();
return (Person) session.get(Person.class, id);
}
public Person add(String firstName, String lastName, Double money) {
Person person = new Person();
person.setFirstName(firstName);
person.setLastName(lastName);
person.setMoney(money);
Session session = sessionFactory.getCurrentSession();
session.save(person);
return person;
}
public Person edit(Integer id, String firstName, String lastName, Double money) {
Person person = get(id);
Session session = sessionFactory.getCurrentSession();
person.setFirstName(firstName);
person.setLastName(lastName);
person.setMoney(money);
session.update(person);
return person;
}
public void delete(Integer id) {
Person person = get(id);
Session session = sessionFactory.getCurrentSession();
session.delete(person);
}
}

위 코드와 기존코드의 가장 큰 차이는 sessionFactory.getCurrentSession()이다. 기존 코드는 sessionFactory.openSession()을 사용하지만, transaction을 사용하는 Service는 sessionFactory.getCurrentSession()이 된다. 그 이유는 다음과 같다. 

Transaction annotation을 사용하는 경우에 annotation에 의해서 다음과 같은 코드로 변경이 되어서 실행이 되는것과 동일해진다. 


public Person edit(Integer id, String firstName, String lastName, Double money) {

Session s = sessionFactory.openSession();
Transaction transaction = s.beginTransaction();

Person person = get(id);
Session session = sessionFactory.getCurrentSession();
person.setFirstName(firstName);
person.setLastName(lastName);
person.setMoney(money);
session.update(person);

transaction.commit();
return person;
}

Transaction AOP에 의해서 항시 openSession, beginTransaction 이 실행되는것과 동일하게 구성이 되게 된다. 그리고, method의 종료시에 transaction의 commit가 자동으로 이루어지기 때문에 Transaction이 구현되는 서비스에서는 sessionFactory에서 getCurrentSession을 통해서 transaction이 적용된 session을 얻어내야지된다.

마지막으로 spring bean annotation설정을 위해서 spring-context.xml에 다음과 같은 내용을 추가하도록 한다.
<!-- sessionFactory에 transaction 구성 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager" 
                         p:sessionFactory-ref="sessionFactory" />
<tx:annotation-driven transaction-manager="transactionManager" />
<!-- sessionFactory 구성 -->

테스트를 구동하고, 정상적으로 동작하는지 확인한다. 



2. Connection Pool의 추가


hibernate에서 주로 사용하는 c3p0를 connection pool로 사용하도록 한다. c3p0는 사용하기 쉽고, 가벼워서 WAS에서 제공하는 connection pool을 사용하지 않는 경우에 주로 추천되는 connection pool이다. 


c3p0 dependency를 pom.xml에 다음과 같이 추가한다.


<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-c3p0</artifactId>

<version>4.1.7.Final</version>

</dependency>



spring-context에서 기존 dataSource를 제거하고 c3p0에서 제공하는 Pooling DataSource로 대체한다. 

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="${app.jdbc.driverClassName}"
p:jdbcUrl="${app.jdbc.url}"
p:user="${app.jdbc.username}"
p:password="${app.jdbc.password}"
p:acquireIncrement="5"
p:idleConnectionTestPeriod="60"
p:maxPoolSize="10"
p:maxStatements="50"
p:minPoolSize="5"/>


테스트를 구동하고, 정상적으로 테스트를 통과하면 완료된다.


일단, 기본 WEB Application을 개발할 수 있는 환경을 만들었지만, 하나 빼먹은 것이 계속해서보인다.; 

그것은 바로... Log!!! 


3. Log의 추가


요즘 java의 추세인 logback와 slf4j를 사용하도록 한다.; 일단 log4j보다 속도가 좋고, 거기에 log4j와 동일한 방법으로 사용이 가능하다는 장점때문에 logback를 slf4j를 이용해서 facade pattern으로 사용하게 된다. 


pom.xml에 다음을 추가한다. 

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-api</artifactId>

<version>${slf4j.version}</version>

</dependency>

<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-classic</artifactId>

<version>${logback.version}</version>

</dependency>


<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-core</artifactId>

<version>${logback.version}</version>

</dependency>


그리고,  resource에 logback.xml 파일을 추가하도록 한다. logback.xml은 기본적으로 logback이 사용될때, 기본적으로 load 되는 설정 xml이다. 주로 사용하는 RollingFileAppender를 이용해서 구성하도록 한다. 


logback의 Appender의 종류와 사용법들은 다음 site에서 참고하도록 한다.

http://logback.qos.ch/manual/appenders.html



<?xml version="1.0" encoding="UTF-8"?>

<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">

<encoder>

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n

</pattern>

</encoder>

</appender>

<appender name="FILEOUT" class="ch.qos.logback.core.rolling.RollingFileAppender">

<file>c:\\Logs\\BackLog\\logFile.log</file>

<maxHistory>30</maxHistory>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>c:\\Logs\\BackLog\\logFile.%d{yyyy-MM-dd}.log</fileNamePattern>

<maxHistory>30</maxHistory>

</rollingPolicy>

<encoder>

<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{5} - %msg%n

</pattern>

</encoder>

</appender>

<root level="INFO">

<appender-ref ref="STDOUT" />

<appender-ref ref="FILEOUT" />

</root>

</configuration>



이제 test를 실행하면 Console과 LogFile이 정상적으로 만들어지는 것이 확인된다. 이로서 개발전 기본 설정은 모두 마쳐지게 된다. 



Posted by Y2K
,

wikibook에 spring과 hibernate를 연결하는 작업을 진행한다.


1. maven dependency 설정

최종적으로는 spring web mvc를 사용할 예정이기 때문에, wikibook project에 다음과 같은 dependency를 추가한다. 


pom.xml

<!-- spring test jar -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-test</artifactId>

<version>3.1.2.RELEASE</version>

<scope>test</scope>

</dependency>

<!-- spring web mvc jar -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-webmvc</artifactId>

<version>3.1.2.RELEASE</version>

</dependency>

<!-- spring orm jar -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-orm</artifactId>

<version>3.1.2.RELEASE</version>

</dependency>

<!-- mysql connector -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>5.1.21</version>

</dependency>

<!-- hibernate jar -->

<dependency>

<groupId>org.hibernate</groupId>

<artifactId>hibernate-core</artifactId>

<version>4.1.7.Final</version>

</dependency>

추가 후, maven eclipse:eclipse goal을 실행해서, eclipse project로 재등록시키고, eclipse에서 refresh를 하면 모든 jar 파일이 올라오는 것을 알 수 있다. 


2. entity class 생성


테스트로 Person이라는 객체를 생성한다. 객체 코드는 다음과 같다.

@Entity

@Table(name="PERSON")

public class Person {

@Id

@GeneratedValue

@Column(name="ID")

private Integer id;

@Column(name="FIRST_NAME")

private String firstName;

@Column(name="LAST_NAME")

private String lastName;

@Column(name="MONEY")

private Double money;

public Integer getId() {

return id;

}

public void setId(Integer 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 Double getMoney() {

return money;

}

public void setMoney(Double money) {

this.money = money;

}

}



3. PersonService를 구성한다. 

기본적인 CRUD만을 가진 Service로 구성한다.


@Service("personService")

public class PersonService implements IPersonService {

@Resource

private SessionFactory sessionFactory;

@SuppressWarnings("unchecked")

public List<Person> getAll() {

Session session = sessionFactory.openSession();

List<Person> persons = (List<Person>) session.createCriteria(Person.class).list();

return persons;

}

public Person get(Integer id) {

Session session = sessionFactory.openSession();

return (Person) session.get(Person.class, id);

}

public Person add(String firstName, String lastName, Double money) {

Person person = new Person();

person.setFirstName(firstName);

person.setLastName(lastName);

person.setMoney(money);

Session session = sessionFactory.openSession();

session.save(person);

return person;

}

public Person edit(Integer id, String firstName, String lastName, Double money) {

Person person = get(id);

Session session = sessionFactory.openSession();

person.setFirstName(firstName);

person.setLastName(lastName);

person.setMoney(money);

session.update(person);

session.flush();

return person;

}

public void delete(Integer id) {

Person person = get(id);

Session session = sessionFactory.openSession();

session.delete(person);

session.flush();

}

}


4. test code를 작성한다.

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration(locations="/spring-context.xml")

public class PersonServiceTest {


@Autowired

private IPersonService personService;

@Test

public void addPersons() {

for(Integer i = 0 ; i < 10 ; i++) {

String firstName = "FIRST_NAME " + i.toString();

String lastName = "LAST_NAME " + i.toString();

Double money = i.doubleValue();

Person addedPerson = personService.add(firstName, lastName, money);

assertThat(addedPerson.getId(), not(0));

}

}

@Test

public void getPerson() {

addPersons();

Person person = personService.getAll().get(0);

Integer id = person.getId();

assertThat(id, not(0));

Person newPerson = personService.get(id);

assertThat(person.getId(), is(newPerson.getId()));

}

@Test

public void editPerson() {

addPersons();

Person person = personService.getAll().get(0);

Integer id = person.getId();

assertThat(id, not(0));

String editedFirstName = person.getFirstName() + "__edited";

personService.edit(id, editedFirstName, person.getLastName(), person.getMoney());

Person editedPerson = personService.get(id);

assertThat(editedPerson.getFirstName(), is(editedFirstName));

}

@Test

public void deletePerson() {

addPersons();

Person person = personService.getAll().get(0);

Integer id = person.getId();

assertThat(id, not(0));

personService.delete(id);

assertNull(personService.get(id));

}

}


5. test code를 작성하면 maven 설정에 따라, src/test/resources 에 다음 파일들을 위치시킨다. 


spring-context.xml : spring bean 설정

spring.properties : spring bean에 설정된 properties 파일

hibernate.cfg.xml : hibernate dialect 등 option 정보


spring-context.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:aop="http://www.springframework.org/schema/aop"

xmlns:mvc="http://www.springframework.org/schema/mvc"

xmlns:context="http://www.springframework.org/schema/context"

xmlns:p="http://www.springframework.org/schema/p"

xmlns:tx="http://www.springframework.org/schema/tx"

xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd

http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd

http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd

http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

<!-- person Service bean 정의 -->

<bean id="personService" class="com.xyzlast.services.PersonService"/>

<!-- property file load -->

<context:property-placeholder location="spring.properties" />

<!-- sessionFactory 구성 -->

<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"

p:dataSource-ref="dataSource"

p:configLocation="${hibernate.config}"

p:packagesToScan="com.xyzlast.entities"/>

<!-- MySql dataSource 구성 -->

<bean id="dataSource"

class="org.springframework.jdbc.datasource.DriverManagerDataSource"> 

<property name="driverClassName" value="${app.jdbc.driverClassName}" />

<property name="url" value="${app.jdbc.url}" />

<property name="username" value="${app.jdbc.username}" />

<property name="password" value="${app.jdbc.password}" />

</bean>

</beans>


spring.properties

# database properties

app.jdbc.driverClassName=com.mysql.jdbc.Driver

app.jdbc.url=jdbc:mysql://localhost:3306/test

app.jdbc.username=root

app.jdbc.password=qwer12#$

 

#hibernate properties

hibernate.config=hibernate.cfg.xml



hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

<property name="show_sql">true</property>

<property name="hbm2ddl.auto">create</property>

<property name="hibernate.connection.release_mode">after_transaction</property>

</session-factory>

</hibernate-configuration>


6. 테스트를 실행한다. 

eclipse JUnit plugin을 이용하건, maven command를 이용하던, 모든 방법에서 test 가 ok가 나와야지 된다.






다음은 Controller 구성과 REST 서비스를 구현해보도록 한다. 

Posted by Y2K
,