6-20-1. Spring Framework RESTful 지원 REST API는웹서비스를개발하는표준방법이되었고자바와관련하여사용할수있는많은프레임워크와라이브러리가있다. JAX-RS, Restlet, Jersey, RESTEasy, Apache CFX 등이있지만 Java 개발자는 Spring MVC를사용하여 RESTful 웹서비스를개발하는것이좋다. RESTful 웹서비스를개발하기위해 Spring을사용하는가장중요한이유는 RESTful 웹서비스를개발하기위해 Spring MVC 환경을사용할수있고새로운프레임워크또는라이브러리를배울필요가없기때문이다. Spring3.0부터시작된지난몇몇버전에서는 REST 지원을제공하기위해 Spring MVC에많은개선점을제공했는데, 우선전용주석을제공하는데 @RestController와 @ResponseStatus를사용하면 Spring 4.0에서 RESTful 리소스를더욱쉽게개발할수있다. RESTful 웹서비스를만드는데도움이될뿐만아니라 REST 리소스를소비하는클래스도제공한다. RestTemplate 클래스를사용하여 RESTful 리소스를사용할수있다. 스프링프레임워크는 RESTful 웹서비스를두가지형태로지원한다. MVC에서 ModelAndView를아용 HTTP Message Converters를이용 ModelAndView 접근방식은오래되었고문서화가훨씬쉬우면서도보다상세하고구성이무거움, REST 패러다임을구형모델에넣으려고하여애로사항있음, Spring 팀은이것을이해하고 Spring 3.0부터는 REST 지원을제공했다. HttpMessageConverter와어노테이션에기반한새로운접근방식은훨씬가볍고구현하기쉽다. 구성이최소화되어 RESTful 서비스에서기대할수있는적절한기본값을제공한다. @EnableWebMvc는 REST의경우클래스패스에 Jackson과 JAXB 2가있는지감지하고기본 JSON 및 XML 변환기를자동으로만들고등록한다. 어노테이션의기능은 XML 버전과동일합니다. <mvc : annotation-driven /> Spring MVC에서컨트롤러는 RESTful 웹서비스의백본인모든 HTTP 메소드에대한요청을처리할수있다. 예를들어, 읽기조작을수행하는 GET 메소드, 자원을작성하는 POST 메소드, 자원을갱신하는 PUT 메소드및서버에서자원을제거하는 DELETE 메소드를처리할수있다. REST의경우데이터표현이매우중요하므로스프링 MVC를사용하면 @ResponseBody 주석및다양한 HttpMessgeConverter 구현을사용하여뷰기반렌더링을모두건너뛸수있다. 이를사용하여클라이언트에직접응답을보낼수있으며자원클라이언트는원하는형식으로원하는자원을선택해야한다. Spring 4.0 릴리스에는 RESTful 웹서비스개발을쉽게하기위해 @RestController라는전용주석이추가되어 @Controller 대신 @RestController를컨트롤러클래스에사용하면 Spring 은메시지대화 ( 요청메소드 ) 를컨트롤러의모든핸들러메소드에적용하며컨트롤러모든메소드에 @ResponseBody 어노테이션을추가할필요가없다. 이것은또한코드를훨씬깔끔하게만들며모든응답이브라우저바디에직접쓰여진다. REST 웹서비스와일반적인웹애플리케이션간의주요차이점중하나는 REST API의요청
은리소스가 URI 자체의데이터를식별한다. ( URI는 /messages/101 이며 GET, POST, DELETE 에따라다르게동작한다, 일반적인웹응용프로그램은매개변수를사용한다. /messages?id=101.) 파라미터가기억나면 @RequestParam을사용하여쿼리매개변수의값을가져오지만 Spring MVC는 URL 경로에서데이터를추출할수있는 @PathVariable 주석을제공한다. 컨트롤러는매개변수화된 URL에대한요청을처리할수있다. RESTful 웹서비스의또다른주요측면은 Representation이다. 동일한리소스가 JSON, XML, HTML 등다른형식으로표현될수있음을의미한다. 고맙게도 Spring은여러뷰구현과뷰를제공한다. HttpMessageConverts를사용하여클라이언트가원하는형식으로응답을변환하는데사용되는 @ResponseBody와마찬가지로 Spring MVC는 HttpMethodConverter 구현을사용하여인바운드 HTTP 데이터를컨트롤러의핸들러에전달된 Java 객체로변환하는 @RequestBody를제공한다. 스프링프레임워크는또한 JdbcTemplate과유사한템플릿클래스인 RestTemplate과 REST 리소스를사용할수있는 JmsTemplate을제공한다. 이클래스를사용하여 RESTful 웹서비스를테스트하거나 REST 클라이언트를개발할수있다. Spring MVC를사용하여 Spring Restful Web Services를작성한다음 Rest 클라이언트로테스트해보자. 마지막으로 Spring RestTemplate API를사용하여 Spring Restful 웹서비스를호출하는방법에대해서도살펴보자. Spring Jackson JSON 통합을사용하여나머지호출응답에서 JSON 응답을보낸다. 스프링 STS IDE에서스프링 MVC 스켈레톤코드를쉽게생성하고 Restful 아키텍처를구현한다. 6-20-2. Spring MVC REST 실습 (Hibernate, RestTemplate) Spring Legacy Project >> Spring MVC Project( 프르젝트명 : restapi, top level package : restapi) pom.xml <?xml version="1.0" encoding="utf-8"?> <project xmlns="http://maven.apache.org/pom/4.0.0" xmlns:xsi="http://www.w3.org/2001/xmlschema-instance" xsi:schemalocation="http://maven.apache.org/pom/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelversion>4.0.0</modelversion> <groupid>a.b</groupid> <artifactid>restapi</artifactid> <name>restapi</name> 메모포함 [W 사 1]:
<packaging>war</packaging> <version>1.0.0-build-snapshot</version> <properties> <java-version>1.6</java-version> <org.springframework-version>4.3.0.release</org.springframeworkversion> <org.aspectj-version>1.6.10</org.aspectj-version> <org.slf4j-version>1.6.6</org.slf4j-version> </properties> <dependencies> <!-- Spring --> <groupid>org.springframework</groupid> <artifactid>spring-context</artifactid> <version>${org.springframework-version</version> <exclusions> <!-- Exclude Commons Logging in favor of SLF4j --> <exclusion> <groupid>commons-logging</groupid> <artifactid>commons-logging</artifactid> </exclusion> </exclusions> <groupid>org.springframework</groupid> <artifactid>spring-webmvc</artifactid> <version>${org.springframework-version</version> <groupid>org.springframework</groupid> <artifactid>spring-orm</artifactid> <version>${org.springframework-version</version>
<!-- Hibernate ORM --> <groupid>org.hibernate</groupid> <artifactid>hibernate-core</artifactid> <version>5.2.11.final</version> <!-- Hibernate-C3P0 Integration --> <groupid>org.hibernate</groupid> <artifactid>hibernate-c3p0</artifactid> <version>5.2.11.final</version> <!-- Mysql Connector --> <groupid>mysql</groupid> <artifactid>mysql-connector-java</artifactid> <version>6.0.5</version> <!-- Jackson API for JSON --> <groupid>com.fasterxml.jackson.core</groupid> <artifactid>jackson-databind</artifactid> <version>2.8.7</version> <!-- AspectJ --> <groupid>org.aspectj</groupid> <artifactid>aspectjrt</artifactid> <version>${org.aspectj-version</version> <!-- Logging -->
<groupid>org.slf4j</groupid> <artifactid>slf4j-api</artifactid> <version>${org.slf4j-version</version> <groupid>org.slf4j</groupid> <artifactid>jcl-over-slf4j</artifactid> <version>${org.slf4j-version</version> <scope>runtime</scope> <groupid>org.slf4j</groupid> <artifactid>slf4j-log4j12</artifactid> <version>${org.slf4j-version</version> <scope>runtime</scope> <groupid>log4j</groupid> <artifactid>log4j</artifactid> <version>1.2.15</version> <exclusions> <exclusion> <groupid>javax.mail</groupid> <artifactid>mail</artifactid> </exclusion> <exclusion> <groupid>javax.jms</groupid> <artifactid>jms</artifactid> </exclusion> <exclusion> <groupid>com.sun.jdmk</groupid> <artifactid>jmxtools</artifactid> </exclusion> <exclusion>
<groupid>com.sun.jmx</groupid> <artifactid>jmxri</artifactid> </exclusion> </exclusions> <scope>runtime</scope> <!-- @Inject --> <groupid>javax.inject</groupid> <artifactid>javax.inject</artifactid> <version>1</version> <!-- Servlet --> <groupid>javax.servlet</groupid> <artifactid>servlet-api</artifactid> <version>2.5</version> <scope>provided</scope> <groupid>javax.servlet.jsp</groupid> <artifactid>jsp-api</artifactid> <version>2.1</version> <scope>provided</scope> <groupid>javax.servlet</groupid> <artifactid>jstl</artifactid> <version>1.2</version> <!-- Test -->
<groupid>junit</groupid> <artifactid>junit</artifactid> <version>4.7</version> <scope>test</scope> </dependencies> <build> <plugins> <plugin> <artifactid>maven-eclipse-plugin</artifactid> <version>2.9</version> <configuration> <additionalprojectnatures> > <projectnature>org.springframework.ide.eclipse.core.springnature</projectnature </additionalprojectnatures> <additionalbuildcommands> <buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcomm and> </additionalbuildcommands> <downloadsources>true</downloadsources> <downloadjavadocs>true</downloadjavadocs> </configuration> </plugin> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-compiler-plugin</artifactid> <version>2.5.1</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerargument>- Xlint:all</compilerArgument>
<showwarnings>true</showwarnings> <showdeprecation>true</showdeprecation> </configuration> </plugin> <plugin> <groupid>org.codehaus.mojo</groupid> <artifactid>exec-maven-plugin</artifactid> <version>1.2.1</version> <configuration> <mainclass>org.test.int1.main</mainclass> </configuration> </plugin> <plugin> <groupid>org.apache.maven.plugins</groupid> <artifactid>maven-war-plugin</artifactid> <version>2.6</version> <configuration> <! web.xml 없이프로젝트를운영하기위해 <failonmissingwebxml>false</failonmissingwebxml> </configuration> </plugin> </plugins> </build> </project> 메모포함 [W 사 2]: web.xml 없이프로젝트를운영하기 위해 /src/main/resources/db.properties # MySQL properties mysql.driver=com.mysql.cj.jdbc.driver mysql.url=jdbc:mysql://localhost:3306/restapi?servertimezone=utc&createdat abaseifnotexist=true mysql.user=root mysql.password=1234 # Hibernate properties hibernate.show_sql=true hibernate.hbm2ddl.auto=create-drop #C3P0 properties hibernate.c3p0.min_size=5
hibernate.c3p0.max_size=20 hibernate.c3p0.acquire_increment=1 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statements=150 /src/main/resources/import.sql(emp 테이블에초기데이터삽입 ) insert into emp values (1,'1길동',5000000); insert into emp values (2,'2길동',6000000); insert into emp values (3,'3길동',7000000); insert into emp values (4,'4길동',5500000); insert into emp values (5,'5길동',4500000); restapi.config.appconfig.java package restapi.config; import static org.hibernate.cfg.availablesettings.c3p0_acquire_increment; import static org.hibernate.cfg.availablesettings.c3p0_max_size; import static org.hibernate.cfg.availablesettings.c3p0_max_statements; import static org.hibernate.cfg.availablesettings.c3p0_min_size; import static org.hibernate.cfg.availablesettings.c3p0_timeout; import static org.hibernate.cfg.availablesettings.driver; import static org.hibernate.cfg.availablesettings.hbm2ddl_auto; import static org.hibernate.cfg.availablesettings.pass; import static org.hibernate.cfg.availablesettings.show_sql; import static org.hibernate.cfg.availablesettings.url; import static org.hibernate.cfg.availablesettings.user; import java.util.properties; import org.springframework.beans.factory.annotation.autowired; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.componentscan; import org.springframework.context.annotation.componentscans; import org.springframework.context.annotation.configuration; import org.springframework.context.annotation.propertysource;
import org.springframework.core.env.environment; import org.springframework.orm.hibernate5.hibernatetransactionmanager; import org.springframework.orm.hibernate5.localsessionfactorybean; import org.springframework.transaction.annotation.enabletransactionmanagement; @Configuration @PropertySource("classpath:db.properties") @EnableTransactionManagement // 트랜잭션가능하도록 @ComponentScans(value = { @ComponentScan("restapi.repository"), @ComponentScan("restapi.service") ) public class AppConfig { @Autowired private Environment env; @Bean public LocalSessionFactoryBean getsessionfactory() { LocalSessionFactoryBean factorybean = new LocalSessionFactoryBean(); Properties props = new Properties(); // Setting JDBC properties props.put(driver, env.getproperty("mysql.driver")); props.put(url, env.getproperty("mysql.url")); props.put(user, env.getproperty("mysql.user")); props.put(pass, env.getproperty("mysql.password")); // Setting Hibernate properties props.put(show_sql, env.getproperty("hibernate.show_sql")); props.put(hbm2ddl_auto, env.getproperty("hibernate.hbm2ddl.auto")); // Setting C3P0 properties props.put(c3p0_min_size, env.getproperty("hibernate.c3p0.min_size")); props.put(c3p0_max_size, env.getproperty("hibernate.c3p0.max_size")); props.put(c3p0_acquire_increment, env.getproperty("hibernate.c3p0.acquire_increment"));
props.put(c3p0_timeout, env.getproperty("hibernate.c3p0.timeout")); props.put(c3p0_max_statements, env.getproperty("hibernate.c3p0.max_statements")); factorybean.sethibernateproperties(props); factorybean.setpackagestoscan("restapi.model"); return factorybean; @Bean public HibernateTransactionManager gettransactionmanager() { HibernateTransactionManager transactionmanager = new HibernateTransactionManager(); transactionmanager.setsessionfactory(getsessionfactory().getobject()); return transactionmanager; restapi.config.dispatcherconfig.java( 디스패처서블릿의설정을대체 ) package restapi.config; import org.springframework.context.annotation.componentscan; import org.springframework.context.annotation.componentscans; import org.springframework.context.annotation.configuration; import org.springframework.web.servlet.config.annotation.enablewebmvc; import org.springframework.web.servlet.config.annotation.webmvcconfigureradapter; @Configuration @EnableWebMvc @ComponentScans(value = { @ComponentScan("restapi.controller") ) public class DispatcherConfig extends WebMvcConfigurerAdapter { restapi.config.mywebinitializer.java(web.xml 을대체 ) package restapi.config;
import org.springframework.web.servlet.support.abstractannotationconfigdispatcher ServletInitializer; public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { protected Class<?>[] getrootconfigclasses() { return new Class[] { AppConfig.class ; protected Class<?>[] getservletconfigclasses() { return new Class[] { DispatcherConfig.class ; protected String[] getservletmappings() { return new String[] { "/" ; restapi.model.emp.java( 롬복사용 ) package restapi.model; import javax.persistence.entity; import javax.persistence.generatedvalue; import javax.persistence.generationtype; import javax.persistence.id; import lombok.data; @Data @Entity(name = "Emp") //Emp라는이름으로 respapi DB에데이블을생성 public class Emp { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long empno; private String ename; private Integer sal;
restapi.repository.empdao.java package restapi.repository; import java.util.list; import restapi.model.emp; public interface EmpDao { long save(emp emp); Emp get(long empno); List<Emp> list(); void update(long empno, Emp emp); void delete(long empno); restapi.repository.empdaoimpl.java package restapi.repository; import java.util.list; import javax.persistence.criteria.criteriabuilder; import javax.persistence.criteria.criteriaquery; import javax.persistence.criteria.root; import org.hibernate.query; import org.hibernate.session; import org.hibernate.sessionfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.repository;
import restapi.model.emp; @Repository public class EmpDaoImpl implements EmpDao { @Autowired private SessionFactory sf; public long save(emp emp) { sf.getcurrentsession().save(emp); return emp.getempno(); public Emp get(long empno) { return sf.getcurrentsession().get(emp.class, empno); public List<Emp> list() { Session session = sf.getcurrentsession(); CriteriaBuilder cb = session.getcriteriabuilder(); CriteriaQuery<Emp> cq = cb.createquery(emp.class); Root<Emp> root = cq.from(emp.class); cq.select(root); Query<Emp> query = session.createquery(cq); return query.getresultlist(); public void update(long empno, Emp emp) { Session session = sf.getcurrentsession(); Emp emp2 = session.byid(emp.class).load(empno); emp2.setename(emp.getename()); emp2.setsal(emp.getsal()); session.flush();
public void delete(long empno) { Session session = sf.getcurrentsession(); Emp emp = session.byid(emp.class).load(empno); session.delete(emp); restapi.service.empservice.java package restapi.service; import java.util.list; import restapi.model.emp; public interface EmpService { long save(emp emp); Emp get(long empno); List<Emp> list(); void update(long empno, Emp emp); void delete(long empno); restapi.service.empserviceimpl.java package restapi.service; import java.util.list; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.service; import org.springframework.transaction.annotation.transactional;
import restapi.model.emp; import restapi.repository.empdao; @Service @Transactional(readOnly = true) public class EmpServiceImpl implements EmpService { @Autowired private EmpDao empdao; public long save(emp emp) { return empdao.save(emp); public Emp get(long empno) { return empdao.get(empno); public List<Emp> list() { return empdao.list(); @Transactional(readOnly = false) public void update(long empno, Emp emp) { empdao.update(empno, emp); @Transactional(readOnly = false) public void delete(long empno) { empdao.delete(empno); restapi.controller.java package restapi.controller; import java.util.list; import org.springframework.beans.factory.annotation.autowired; import org.springframework.http.responseentity; import org.springframework.web.bind.annotation.deletemapping; import org.springframework.web.bind.annotation.getmapping; import org.springframework.web.bind.annotation.pathvariable; import org.springframework.web.bind.annotation.postmapping;
import org.springframework.web.bind.annotation.putmapping; import org.springframework.web.bind.annotation.requestbody; import org.springframework.web.bind.annotation.restcontroller; import restapi.model.emp; import restapi.service.empservice; @RestController public class EmpController { @Autowired private EmpService empservice; // 신규사원추가 @PostMapping("/emp") public ResponseEntity<String> save(@requestbody Emp emp) { long empno = empservice.save(emp); return ResponseEntity.ok().body("New Emp has been saved with EMPNO:" + empno); // empno 로사원조회 @GetMapping("/emp/{empno") public ResponseEntity<Emp> get(@pathvariable("empno") long empno) { Emp emp = empservice.get(empno); if (emp == null) { System.out.println("Emp with empno " + empno + " not found!"); return new ResponseEntity<Emp>(HttpStatus.NOT_FOUND); return ResponseEntity.ok().body(emp); // 모든사원조회 @GetMapping("/emp") public ResponseEntity<List<Emp>> list() { List<Emp> emps = empservice.list(); return ResponseEntity.ok().body(emps); // empno, EMP 객체를받아해당사원수정 @PutMapping("/emp/{empno") public ResponseEntity<?> update(@pathvariable("empno") long empno, @RequestBody Emp emp) { empservice.update(empno, emp); return ResponseEntity.ok().body("Emp has been updated successfully."); // empno 를받아사원삭제 @DeleteMapping("/emp/{empno")
public ResponseEntity<?> delete(@pathvariable("empno") long empno) { empservice.delete(empno); return ResponseEntity.ok().body("Emp has been deleted successfully."); [Chrome Advanced Rest Client 를이용하여테스트 ] 1. 전체사원조회
2. 2 번사원조회
3. 2 번사원삭제
4. POST 방식으로한건의사원데이터를입력했다.
이번에는 RestTemplate 을이용하여 RestClient 를만들어보자. 테스트전에앞에서작성한프로젝트를실행하여 TOMCAT 을시작시키자. 1. EmpTest.java package a.b.restapi; import static org.hamcrest.corematchers.equalto; import static org.junit.assert.assertthat; import org.junit.test; import org.springframework.http.httpstatus; import org.springframework.http.responseentity; import org.springframework.web.client.resttemplate; import restapi.model.emp; public class EmpTest { @Test public void test() { System.out.println("------------------");
Emp.class); RestTemplate rt = new RestTemplate(); String url = "http://localhost:8080/restapi/emp"; ResponseEntity<?> res1 = rt.getforentity(url + "/3", System.out.println(res1); assertthat(res1.getstatuscode(), equalto(httpstatus.ok)); Emp e1 = new Emp(); e1.setename(" 박길홍 "); e1.setsal(6000000); String res2 = rt.postforobject(url, e1, String.class); System.out.println(res2); // rt.delete(url + "9999"); [ 결과 ] <200 OK,Emp(empno=3, ename=3길동, sal=7000000),{content- Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Sun, 10 Jun 2018 08:27:32 GMT]> New Emp has been saved with EMPNO : 6