理想蜗牛@*

***用心生活***

   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  2 随笔 :: 0 文章 :: 1 评论 :: 0 Trackbacks

Struts2+Spring+Hibernate整合
   


  本文主要目的是通过整合Struts2,Spring,Hibernate,开发出一个简易的图书管理系统,该系统具有增删改查的基本功能。


首先准备所需的JAR包,如下:

把jar包拷贝到WEB-INF/lib下,然后在工程中引用这些包。
注意该处使用的Struts版本为2.0.11.2,Spring版本为2.5.5,Hibernate版本为3.3.1


修改web.xml:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
 xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
 http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
 
 <!-- spring的applicationContext载入 -->
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring/*.xml</param-value>
 </context-param>
 <!-- 定义log4j配置文件所在位置 -->
 <context-param>
  <param-name>log4jConfigLocation</param-name>
  <param-value>/WEB-INF/classes/log4j.properties</param-value>
 </context-param>
 
 <filter>
  <filter-name>struts2</filter-name>
  <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
 </filter>
 <!-- 这里用的是spring自带的编码过滤器,不用自己写了,其中主要代码
   是request.setCharacterEncoding(encoding)和
   response.setCharacterEncoding(encoding),对请求和响应设置编码。
   有两个参数:encoding和forceEncoding,其中forceEncoding默认是false,
   (此种情况不能改变response的编码方式)如果设置成true则不管请求是否已有编码
   都强制转换成encoding指定的编码,同时在响应可设置编码的前提下设置响应编码。 -->
 <filter>
  <filter-name>encodingFilter</filter-name>
  <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
  <init-param>
   <param-name>encoding</param-name>
   <param-value>utf-8</param-value>
  </init-param>
  <init-param>
   <param-name>forceEncoding</param-name>
   <param-value>true</param-value>
  </init-param>
 </filter>
 
 <filter-mapping>
  <filter-name>struts2</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping>
 <filter-mapping>
  <filter-name>encodingFilter</filter-name>
  <url-pattern>/*</url-pattern>
 </filter-mapping> 
  
 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener> 
 <!-- 
  web应用关闭时IntrospectorCleanupListener类负责对javabean
  内省缓存进行清除;该类实现接口ServletContextListener,
  对缓存在context中的javabean类进行清除操作的。
  -->
 <listener>
  <listener-class>org.springframework.web.util.IntrospectorCleanupListener</listener-class>
 </listener>
 <!-- Listener log4jConfigLocation -->
   <listener>
     <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
   </listener>
    
   <!-- 定义session的超时时间,单位为分钟 -->
 <session-config>
  <session-timeout>10</session-timeout>
 </session-config>
 
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
</web-app>

几点需要注意的地方:1)对于spring的配置文件和log4j日志配置文件的读取我们用的是spring的ContextLoaderListener和Log4jConfigListener这两个个监听器。2)对浏览器发送的请求,通过配置的Struts2的过滤器(FilterDispatcher)可以对不同的url进行过滤,此处我们配置处理该web应用的所有请求。3)配置了一个spring自带的编码过滤器,用来处理请求和响应的编码,本应用配置对所有请求和响应都采用该编码过滤器,这里我们采用的utf-8。

在WEB-INF/spring/目录下新建applicationContext.xml,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!-- 该处的default-autowire对所有bean提供一种默认的自动装配方式,如果bean需要不同的方式,
可以在bean中配置配置autowire属性。自动装配方式有以下几种:
1) no 不使用自动装配。必须通过ref元素指定依赖,这是默认设置。
2) byName 根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配。
3) byType 如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。
4) constructor 与byType的方式类似,不同之处在于它应用于构造器参数。
5) autodetect 通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动装配。 -->
<beans>
 <!-- 如果实体定义用的是xml配置文件,则sessionFactory的class
  应为"org.springframework.orm.hibernate3.LocalSessionFactoryBean" -->
 <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
  <property name="configLocation">
   <value>classpath:hibernate.cfg.xml</value>
  </property>
   <!--   配置多个hibernate.cfg.xml
        <property name="configLocations">
           <list>
             <value>classpath:hibernate_admin1.cfg.xml</value>
             <value>classpath:hibernate_admin2.cfg.xml</value>
            </list>
        </property>
        -->
 </bean>
 
 <!-- 配置事务管理 -->
 <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="sessionFactory"></property>
 </bean>
 
 <!-- 这里的abstract属性说明该bean是抽象的,不能实例化,只能用来被继承,在这里他被BookService继承 -->
 <bean id="baseTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
  <property name="transactionManager" ref="transactionManager"></property>
  <property name="transactionAttributes">
   <props>
    <!-- PROPAGATION_REQUIRED代表必须在事务中执行,-Exception表示
    当有Exception抛出时事务回滚 -->
    <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="persist*">PROPAGATION_REQUIRED,-Exception</prop>
                <prop key="remove*">PROPAGATION_REQUIRED,-Exception</prop>
   </props>
  </property>
 </bean>
 <bean id="BookService" parent="baseTransactionProxy">
  <property name="target">
   <bean class="com.css.ravollen.sshtest.service.impl.BookServiceImpl">
    <property name="dao">
     <bean class="com.css.ravollen.sshtest.dao.impl.BookDAOImpl">
       <property name="sessionFactory" ref="sessionFactory"/>    
      </bean>
    </property>
   </bean>
  </property>
 </bean>
 <bean id="BookAction" class="com.css.ravollen.sshtest.action.BookAction">
  <property name="bookService" ref="BookService"></property>
 </bean>
</beans>

这里几点要注意的地方:1)sessionFactory的真正定义是在hibernate.cfg.xml里面,sessionFactory Bean通过configLocation属性提供的路径去找,因为hibernate.cfg.xml是在/WEB-INF/classes/下,所以可以通过classpath:hibernate.cfg.xml定位。2)该处配置的sessionFactory是基于Annotation对实体进行管理的。3)对于持久化操作可以配置事务管理机制保证数据的安全和合法,本实例对于目标类(BookServiceImpl)中的以find,persist,remove开头的方法都采用事务机制。

接下来看WEB-INF/classes/下的hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
 <session-factory>
  <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
  <property name="hibernate.connection.username">ravollen</property>
  <property name="hibernate.connection.password">726321</property>
  <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/book?useUnicode=true&amp;characterEncoding=UTF-8</property>
  <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
  <property name="hibernate.show_sql">true</property>
    <!-- 
      该属性默认为none,有以下几种:
      1)validate 加载hibernate时,验证创建数据库表结构
      2) create 每次加载hibernate,重新创建数据库表结构,这就是导致数据库表数据丢失的原因。
      3) create-drop 加载hibernate时创建,退出是删除表结构
      4) update 加载hibernate自动更新数据库结构
   -->
  <!--
  <property name="hibernate.hbm2ddl.auto">create</property>
    -->
    <!--
    这里由于我们采用的是annotation方式,所以直接指向实体对应的类即可,如果是采用实体配置文件则写成
    <mapping resource="com/css/ravollen/sshtest/entity/Book.hbm.xml"></mapping>
     -->
  <mapping class="com.css.ravollen.sshtest.entity.Book"></mapping>
 </session-factory>
</hibernate-configuration>

该文件主要配置了数据库底层连接的一些信息。需要注意的地方有:1)数据库url需加上useUnicode=true&amp;characterEncoding=UTF-8,可以解决中文乱码问题。(其中&amp;是&的转义符)
2)属性"hibernate.hbm2ddl.auto"可以用来建数据库,省事不少,但一定要在第一次运行后注销掉该属性否则每次启动该应用都会把建好的数据库删掉造成数据丢失。3)由于我们采用的annotation方式(从jdk5开始)配置实体,所以mapping属性通过class子属性找到对应的实体类。

下面是WEB-INF/classes/下的struts.xml配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
 <!-- extends说明该配置文件继承自struts-default.xml -->
 <package name="test" extends="struts-default">
  <global-results>
   <result name="listAllBooks">/WEB-INF/ui/booklist.jsp</result>
   <result name="bookInfo">/WEB-INF/ui/editBook.jsp</result>
  </global-results>
  <action name="LoadBookById" class="BookAction" method="findBookById"></action>
  <action name="LoadBookByParam" class="BookAction" method="findBookByParam"></action>
  <action name="LoadAllBooks" class="BookAction" method="findAllBooks"></action>
  <action name="editBook" class="BookAction" method="loadBook"></action>
  <action name="saveBook" class="BookAction" method="saveBook">
    <!--
  都是struts2的默认拦截器
  param拦截器 将Request请求的参数设置到相应Action对象的属性中
  validation拦截器  实现使用xml配置文件({Action}-validation.xml)对Action属性值进行验证
   -->
   <interceptor-ref name="params" />
   <interceptor-ref name="validation" />
   <result name="input">/editBook.jsp</result>
   <result name="success" type="redirect">LoadAllBooks.action</result>
  </action>
  <action name="removeBook" class="BookAction" method="removeBook">
  </action>
 </package>
</struts>

注意这几项:1)package的extends属性说明该配置文件是继承自strut-default.xml,该配置文件在struts2-core-2.0.11.2.jar包内。2)对于大量重复的result,我们可以配置成全局的result,可以减少文件冗余。3)在"保存书"这个活动中,加入了struts2的校验框架,在该action中加入两个拦截器"params"和“validation"就可以,此外在该Action的同一级目录下新建一个校验配置文件即可,命名规则:活动名+"-"+方法名+"-"+"validation.xml,如果该校验文件引用了消息文件,则还要在同一级目录下建一个.properties的消息文件文件,前缀与活动名一样。(需要对消息文件进行ascii码转换,命令为 native2ascii "源文件" "新文件")


在src目录的dao包(可自定义,只要与配置文件的路径相同就行)下定义BookDAO接口和实现该接口的BookDAOImpl类,dao数据访问层主要通过Hibernate进行数据的增删改查的操作。如下:

接口:
package com.css.ravollen.sshtest.dao;
import java.util.List;
import com.css.ravollen.sshtest.entity.Book;

public interface BookDAO {
 public Book findBookById(Long id);
 public List<Book> findBookByParam(String type,String value);
 public List<Book> findAllBooks();
 public void persistBook(Book book);
 public void removeBook(Book book);
 public void removeById(Long id);
}

实现类:

package com.css.ravollen.sshtest.dao.impl;

import java.util.List;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import com.css.ravollen.sshtest.dao.BookDAO;
import com.css.ravollen.sshtest.entity.Book;

public class BookDAOImpl extends HibernateDaoSupport implements BookDAO {
 public BookDAOImpl() {
  super();
 }
 
/**
  * 通过自定义类型查找,比如可通过书名,作者查找
  */

 public List<Book> findBookByParam(final String type,final String value) {
  // TODO Auto-generated method stub
  String sql="";
  sql="FROM Book where "+type+" like '%"+value+"%'"+"ORDER BY bookName";
  return getHibernateTemplate().find(sql);
 }

 public List<Book> findAllBooks() {
  // TODO Auto-generated method stub
  return getHibernateTemplate().loadAll(Book.class);
 }

 public Book findBookById(Long id) {
  // TODO Auto-generated method stub
  return (Book)getHibernateTemplate().get(Book.class, id);
 }

 public void persistBook(Book book) {
  // TODO Auto-generated method stub
  getHibernateTemplate().saveOrUpdate(book);
 }

 public void removeBook(Book book) {
  // TODO Auto-generated method stub
  getHibernateTemplate().delete(book);
 }

 public void removeById(final Long id) {
  // TODO Auto-generated method stub
  getHibernateTemplate().execute(new HibernateCallback() {
   public Object doInHibernate(Session session) {
    session.createQuery("delete from Book o where o.bookId="+id+"").executeUpdate();
    return 1;
   }
  });
 }
}



再看看业务层的services包,该包有BookService接口和实现该接口的BookServiceImpl类,这个包用来衔接数据访问层和表现层,一定程度上起到解耦作用。代码如下:

接口:

package com.css.ravollen.sshtest.service;
import java.util.List;
import com.css.ravollen.sshtest.entity.Book;

public interface BookService {
 public Book findBookById(Long id) throws Exception;
 
 public List<Book> findBookByParam(String type,String value) throws Exception;

 public List<Book> findAllBooks() throws Exception;

 public void persistBook(Book book) throws Exception;

 public void removeBook(Book book) throws Exception;

 public void removeBookById(Long id) throws Exception;
}

实现类:

package com.css.ravollen.sshtest.service.impl;
import java.util.List;
import org.springframework.context.ApplicationContext;
import com.css.ravollen.sshtest.dao.BookDAO;
import com.css.ravollen.sshtest.entity.Book;
import com.css.ravollen.sshtest.service.BookService;

public class BookServiceImpl implements BookService {
 private  BookDAO dao;
 private static final String SERVICE_BEAN_ID = "BookService";
 public BookServiceImpl() {
  super();
 }
 public static BookService getInstance(ApplicationContext context) {
  return (BookService)context.getBean(SERVICE_BEAN_ID);
 }
 public List<Book> findAllBooks() throws Exception {
  try {
   return getDao().findAllBooks();
  }catch(RuntimeException e) {
   throw new Exception("findAllBooks failed :"+e.getMessage());
  }
 }

 public Book findBookById(Long id) throws Exception {
  try {
   return getDao().findBookById(id);
  }catch(RuntimeException e) {
   throw new Exception("findBookById failed with the id " + id + ": " + e.getMessage());
  }
 }
 public List<Book> findBookByParam(String type,String value) throws Exception {
  try {
   return getDao().findBookByParam(type,value);
  }catch(RuntimeException e) {
   throw new Exception("findBookByParam failed with type is :"+type+" and queryContent is:"+value+" :"+e.getMessage());
  }
 }

 public void persistBook(Book book) throws Exception {
  try {
   getDao().persistBook(book);
  }catch(RuntimeException e) {
   throw new Exception("persistBook failed: " + e.getMessage());
  }
 }

 public void removeBook(Book book) throws Exception {
  try {
   getDao().removeBook(book);
  }catch(RuntimeException e) {
   throw new Exception("removeBook failed: " + e.getMessage());
  }
 }

 public void removeBookById(Long id) throws Exception {
  try {
           getDao().removeById(id);
      }catch(RuntimeException e) {
              throw new Exception("removeBookById failed: " + e.getMessage());
      }
 }

 public BookDAO getDao() {
  return dao;
 }

 public void setDao(BookDAO dao) {
  this.dao = dao;
 }
}

注意:1)BookServiceImpl实例通过ApplicationContext从Spring的上下文中得到。2)BookDAO通过Spring的IOC注入到里面。

接下来写BookAction,主要负责处理前台传来的请求,继承自ActionSupport工具类,因而能提供数据校验功能。

package com.css.ravollen.sshtest.action;
import java.util.List;
import org.apache.log4j.Logger;
import com.css.ravollen.sshtest.entity.Book;
import com.css.ravollen.sshtest.service.BookService;
import com.opensymphony.xwork2.ActionSupport;

/**
 * ActionSupport工具类比Action接口多了一个validate()方法,可提供数据校验,
 * 实际上在本例中我们校验采用的是Struts的校验框架,而不是通过改写ActionSupport
 * 的validate()方法去校验数据
 * @author Administrator
 */
public class BookAction extends ActionSupport {

 private static Logger logger = Logger.getLogger(BookAction.class);
 private BookService bookService;
 private Book book;
 private List books;
 private String searchType,searchContent;
 private Long bookId;
 private int addBookFlag;

 public Long getBookId() {
  return bookId;
 }

 public void setBookId(Long bookId) {
  this.bookId = bookId;
 }

 public String getSearchType() {
  return searchType;
 }

 public void setSearchType(String searchType) {
  this.searchType = searchType;
 }

 public String getSearchContent() {
  return searchContent;
 }

 public void setSearchContent(String searchContent) {
  this.searchContent = searchContent;
 }

 public String findBookById(Long id) {
  try {
   this.book = getBookService().findBookById(id);
   return "bookInfo";
  }catch(Exception e) {
   logger.error("BookAction中findBookById()出错!");
   return ERROR;
  }
 }
 
 public String findBookByParam() {
  try {
   this.books = getBookService().findBookByParam(getSearchType(),getSearchContent());
   return "listAllBooks";
  }catch(Exception e) {
   logger.error("BookAction中findBookByParam()出错!");
   return ERROR;
  }
 }
 
 public String findAllBooks() {
  try {
   this.books = getBookService().findAllBooks();
   return "listAllBooks";
  }catch(Exception e) {
   logger.error("BookAction中findAllBooks()出错!");
   return ERROR;
  }
 }
 
 /**
  *
  * @return result对应的名字
  * @throws Exception
  */
 public String loadBook() throws Exception {
  if(getAddBookFlag()==1) {
   addBookFlag = 0;
   book = null;
  }
  else if(bookId!=null) {
   book = getBookService().findBookById(bookId);
  }  
     return "bookInfo";
 }
 public String saveBook() throws Exception {
   getBookService().persistBook(getBook());
  return SUCCESS;
 }
 public String removeBook() throws Exception {
  if(null!=bookId) {
   getBookService().removeBookById(bookId);
  }
  return "listAllBooks";
 }
 
 public Book getBook() {
  return book;
 }
 public void setBook(Book book) {
  this.book = book;
 }
 public List getBooks() {
  return books;
 }
 public void setBooks(List books) {
  this.books = books;
 }
 public BookService getBookService() {
  return bookService;
 }
 public void setBookService(BookService bookService) {
  this.bookService = bookService;
 }

 public int getAddBookFlag() {
  return addBookFlag;
 }

 public void setAddBookFlag(int addBookFlag) {
  this.addBookFlag = addBookFlag;
 }
}

说明一下:1)Struts的拦截器解析前台参数然后将值赋给Action里的对应属性,使得我们不用跟请求和响应直接打交道。2)对于增加书和编辑书我们用同一个方法处理,通过isAddBook来标识, addBookFlag==1是增加,==0是编辑。(不知为何用布尔型来标识前台无法返回正确的布尔值,所以在这里用的是整形

校验框架使用的配置文件命名为BookAction-saveBook-validation.xml,内容如下:

 <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC "-//OpenSymphony Group//XWork Validator 1.0//EN" "http://www.opensymphony.com/xwork/xwork-validator-1.0.dtd">
<validators>
    <!-- Field-Validator Syntax -->
    <field name="book.bookName">
        <field-validator type="requiredstring">
            <message key="book.bookName.required"/>
        </field-validator>
    </field>
</validators>

这里用到了消息资源,因此在同级目录下还要有一个名为BookAction.properties的资源文件。

下面贴出booklist.jsp,基于Struts2强大的标签:

<%@page pageEncoding="gb2312" contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head><title>图书管理系统</title></head>
    <style type="text/css">
        table {
            border: 1px solid black;
            border-collapse: collapse;
        }
       
        table thead tr th {
            border: 1px solid black;
            padding: 3px;
            background-color: #cccccc;
        }
       
        table tbody tr td {
            border: 1px solid black;
            padding: 3px;
        }
    </style>
    <script language="JavaScript">  
     function doSearch(){
   if(document.all.queryContent.value=="")
   { 
    alert("请输入查询关键字!");
   }else{
    window.location.href="LoadBookByParam.action?searchType="+document.all.queryType.value+"&&searchContent="+document.all.queryContent.value;
    }
  }
 </script>
<body>
<table cellspacing="0" align="center">
 <thead>
 <tr>
  <th><a href='<s:url action="editBook"><s:param name="addBookFlag" value="1" /></s:url>'>增加</a></th>
  <th></th><th></th>
 </tr>
 <tr align="center">
  <th>
   <select name="queryType">
    <option value="bookId">Id</option>
    <option value="bookName">书名</option>
   </select>
  </th>
  <th><input type="text" name="queryContent" value="" size="10"/></th>
  <th><input type="button" value="查询" onClick="doSearch();"></th>
 </tr>
    <tr>
        <th>书名</th>
        <th>描述</th>
        <th>删除</th>
    </tr>
    </thead>
    <tbody>
    <s:iterator value="books">
        <tr class="trs">
            <td>
            <a href='<s:url action="editBook" ><s:param name="bookId" value="bookId" /></s:url>'>
            <s:property value="bookName"/>
            </a>
            </td>
            <td><s:property value="description" /></td>
            <td><a href='<s:url action="removeBook"><s:param name="bookId" value="bookId" /></s:url>'>删除</a></td>
        </tr>
    </s:iterator>
     </tbody>
</table>
</body>
</html>


editBook.jsp如下:

<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>

<html>
<head>
 <title>编辑图书</title>
 <s:head/>
</head>
<body>
 <h2>
        <s:if test="null == book">增加图书</s:if>
        <s:else>编辑图书</s:else>
    </h2>
 <s:form name="editForm" action="saveBook" validate="true"> 
   <s:textfield label="书名" name="book.bookName"/>
   <s:textfield label="作者" name="book.author"/>
   <s:textfield label="出版社" name="book.publisher"/>
      <s:datetimepicker label="出版日期" name="book.pubDate"></s:datetimepicker>
   <s:textfield label="内容摘要" name="book.description"/>
   <s:if test="null == book">
    <s:hidden name="book.bookId" value="%{bookId}"/>
   </s:if>   
      <s:else>
    <s:hidden name="book.bookId" />
   </s:else>
   <s:submit value="%{getText('保存')}" />
 </s:form>
<p><a href="<s:url action="LoadAllBooks"/>">返回</a></p>
</body>
</html>



至此我们完成了一个完整的Web Project,把它发布到tomcat下看看吧!(祝你成功)

2008年10月29日17:45:21

posted on 2008-10-29 17:44 理想蜗牛 阅读(2642) 评论(1)  编辑  收藏

评论

# re: struts2+spring+hibernate整合 2009-03-18 18:36 xxhh
com.css.ravollen.sshtest.entity.Book;


在那里在?????  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: