SpringMVC on Hibernate

昨日ポストした内容をSpringMVCでのWebアプリに拡張する。
今回も同様に全てインメモリで作る。使用するのは以下。

  • jetty7
  • hibernate3
  • spring2.5.5
  • h2

プロジェクト構成

Eclipseで新規Javaプロジェクト作成し、「WebContent/WEB-INF/classe」「WebContent/WEB-INF/jsp」フォルダを作成しておく。
出力先フォルダを「/WebContent/WEB-INF/classes」に設定する。

「WEB-INF」直下にweb.xml作成

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>

  <servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>*.form</url-pattern>
  </servlet-mapping>
</web-app>

同じ場所に、springmvc-servlet.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:tx="http://www.springframework.org/schema/tx"
  xmlns:context="http://www.springframework.org/schema/context"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
    http://www.springframework.org/schema/aop
    http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-2.5.xsd">

  <context:component-scan base-package="etc9.dao,etc9.service,etc9.controller"/>
  
  <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.h2.Driver"/>
    <property name="url" value="jdbc:h2:tcp://localhost:9092/spring"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
  </bean>

  <tx:annotation-driven transaction-manager="txManager"/>
  <bean id="txManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
   </bean>
 
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="hibernateProperties">
      <value>
		hibernate.dialect = org.hibernate.dialect.H2Dialect
		hibernate.show_sql = true
		hibernate.format_sql = true
		hibernate.hbm2ddl.auto = create
      </value>
    </property>
    <property name="annotatedClasses">
     <list><value>etc9.domain.Product</value></list>
    </property>
  </bean>
  
</beans>

先に示したライブラリをクラスパスに追加する。jettyはJSPを使用するため、「\lib\jsp-2.1」内のライブラリも追加追加する。(ライブラリ多いので後でApache Ivy導入しよう・)

実装

パッケージは以下を作成することにする。

  • etc9.controller
    • ProductController.java
  • etc9.dao
  • etc9.domain
  • etc9.service
    • ProductService.java
    • ProductServiceImpl.java
  • etc9.tools


ProductController.javaから

package etc9.controller;
・・・
@Controller
public class ProductController {
    @Autowired
    private ProductService indexService;
    
    @RequestMapping("/product")
    public ModelAndView product() {
        List<Product> productList = indexService.invoke("java");
        ModelAndView mav = new ModelAndView();
        mav.setViewName("/WEB-INF/jsp/product.jsp");
        mav.addObject("productList", productList);
        return mav;
    }
}

続いてDao

package etc9.dao;
・・・
public interface ProductDao {
    List<Product> loadProductsByCategory(final String category) throws DataAccessException;
    Product get(Long id);
    void save(Product product);
}
package etc9.dao;
・・・
@Repository
public class ProductDaoImpl implements ProductDao {
    private HibernateTemplate hibernateTemplate;

    @Autowired(required=true)
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
    }
    
    @Override @SuppressWarnings("unchecked")
    public List<Product> loadProductsByCategory(final String category) throws DataAccessException {
        return (List<Product>) this.hibernateTemplate.execute(new HibernateCallback(){
            @Override
            public Object doInHibernate(Session session)
                    throws HibernateException, SQLException {
                Criteria criteria = session.createCriteria(Product.class);
                criteria.add(Restrictions.eq("category", category));
                criteria.setMaxResults(6);
                return criteria.list();
            }});
    }
    
    @Override
    public Product get(final Long id) {
        return (Product) this.hibernateTemplate.get(Product.class, id);
    }
    
    @Override
    public void save(final Product product) {
        this.hibernateTemplate.save(product);
    }
}

続いてdomain

package etc9.domain;
・・・
@Entity
public class Product {
	private Long id;
	private String name;
	private String category;
	private Integer price;
	
	public Product() {}
	public Product(String name, String category, Integer price) {
		this.name = name;
		this.category = category;
		this.price = price;
	}

	@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
	public Long getId() { return id; }
	public void setId(Long id) { this.id = id; }
	
	public String getName() { return name; }
	public void setName(String name) { this.name = name; }

	public String getCategory() { return category; }
	public void setCategory(String category) { this.category = category; }

	public Integer getPrice() { return price; }
	public void setPrice(Integer price) { this.price = price; }
}

続いてサービス

package etc9.service;
・・・
public interface ProductService {
	public List<Product> findProduct(String category);
}
package etc9.service;
・・・
@Service("indexService") @Transactional
public class ProductServiceImpl implements ProductService {
    
    @Autowired(required=true)
    private ProductDao productDao;
    
    @Transactional @Override
    public List<Product> findProduct(String category) {
        Product p1 = new Product("hibernate", "java", 1000);
        Product p2 = new Product("spring", "java", 1500);
        productDao.save(p1);
        productDao.save(p2);
        return productDao.loadProductsByCategory(category);
    }
}

各実装クラスには、@Controller、@Repository、@Serviceのアノテーションを付与し、役割に応じた設定をspringに任せる。このへんの話はspringの本家のドキュメントか、日本語ならhttp://itpro.nikkeibp.co.jp/article/COLUMN/20090604/331310/?ST=develop&P=1とかを参照のこと。

最後にサーバ起動用のランチャーを作成

public class H2Server {
    
    private static final String TCP_PORT = "9092";
    private static final String WEB_PORT = "8082";
    private static final String BASE_DIR = "db\\h2";

    private final org.h2.tools.Server tcpServer;
    private final org.h2.tools.Server webServer;
    
    public H2Server() {
        this(BASE_DIR, TCP_PORT, WEB_PORT);
    }
    public H2Server(String baseDir, String tcpPort, String WebPort) {
        try {
            tcpServer = org.h2.tools.Server.createTcpServer(
                    new String[] { "-baseDir", baseDir, "-tcpPort", tcpPort });
            webServer = org.h2.tools.Server.createWebServer(
                    new String[] { "-baseDir", baseDir, "-tcpPort", WebPort });
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void stop() {
        Exception remindEx = null;
        try {
            tcpServer.stop();
        }catch (Exception e) {
            remindEx = e;
        }
        try {
            webServer.start();
        } catch (Exception e) {
            remindEx = e;
        }
        if (remindEx != null) throw new RuntimeException(remindEx);
    }

    public void start() {
        try {
            tcpServer.start();
            webServer.start();
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
public class JettyServer {

    private static final int PORT = 8080;
    private static final String WAR_PATH = "WebContent";
    private static final String CONTEXT_PATH = "/web";

    private final org.eclipse.jetty.server.Server server;
    
    public JettyServer() {
        this(PORT, WAR_PATH, CONTEXT_PATH);
    }

    public JettyServer(int port, String warPath, String contextPath) {
        server = new org.eclipse.jetty.server.Server(port);
        ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
        server.setHandler(context);
        WebAppContext web = new WebAppContext(warPath, contextPath);
        server.setHandler(web);
    }
    
    public void stop() {
        try {
            server.stop();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void start() {
        try {
            server.start();
            server.join();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

最後にランチャー

package etc9.tools;

public class ServerLauncher {
    public static void main(String[] args) {
        new H2Server().start();
        new JettyServer().start();
    }
}

JSP

WEB_INF/jspにproduct.jspを作成。

<%@ page contentType="text/html;charset=Shift_JIS" %>
<%@ page session="false" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %>
<html>
<head>
<title>商品一覧</title>
</head>
<body>
  <div align="center" class="body">
  <h2>商品一覧</h2>
  <table border="1">
    <tr class="header">
      <th align="center" width="80">商品ID</th>
      <th align="center" width="320">商品名</th>
      <th align="center" width="100">価格</th>
    </tr>
    <c:forEach items="${productList}" var="product">
      <tr class="record">
        <td align="center">
          <c:out value="${product.id}" />
        </td>
        <td align="left">
          <c:out value="${product.name}" />
        </td>
        <td align="right">
          <c:out value="${product.price}" /></td>
      </tr>       
    </c:forEach>
  </table>
  </div>
</body>
</html>

http://localhost:8080/web/product.formにアクセスして商品一覧が表示されれば成功。