Spring+SpringMVC+Hibernate实现投票/调查问卷网站

原文链接:https://zhang35.coding.me/2018-java-web-2-11.html

使用SSH架构(Spring+SpringMVC+Hibernate)实现了简单的调查问卷网站。最终效果如图:


投票页面

查看结果页面

下面整理实现流程。


前言

1.SSH架构

SSH是MVC架构的一种实现。

Spring、SpringMVC、Hibernate各自用处分别是:

  • Hibernate方便了对数据库的操作。一个对象映射一个表,省去了写SQL语句的繁琐,完成数据持久化的任务。
  • Spring方便了对象的创建和相互关联。比如网站启动时想要初始化的一些对象,可交给Spring管理。
  • SpringMVC实现了MVC架构,使得结构清晰、分工明确。

(Spring和SpringMVC区别:Spring是IOC和AOP的容器框架,参考:谈谈Spring中的IOC和AOP概念);SpringMVC是基于Spring实现的MVC Web框架)。

2.Maven

Maven是一个项目管理工具,有一套标准的工程结构。其核心配置文件是pom.xml,描述了项目信息,依赖关系等。

由于Java项目中需要引入各种jar包,还存在版本差异,把这些依赖关系在pom.xml里面描述,maven就会自动从本地或远程仓库寻找依赖,不用再去一个个下载、拷贝jar包了。

例如,想引入springmvc框架,就在pom.xml中加入如下配置:

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${spring.version}</version>
      </dependency

3.代码结构

Java源码包含Model、DAO、Service、Controller四个包,其中:

  • Model:存放数据模型
  • DAO:实现直接操作Model的接口及方法,比如实现getPerson()
  • Service:使用DAO提供的接口,实现项目需要用到的功能,比如实现getAllPersons()
  • Controller:使用Service提供的功能,实现数据分发及页面展示。

工程结构如下:

工程结构

项目源码:https://github.com/zhang35/QuizWeb.git


开发环境

集成开发环境(IDE):IntelliJ IDEA 2017.3.2
本地服务器:Tomcat 9.0.2
数据库: MySQL 5.7
项目管理:Maven
操作系统:MacOS


开发步骤

1. 准备工作

2. 搭建SSH项目

3. 实现投票功能

从操作流程出发,实现思路是:

3.1 输入网址进入投票界面。(SpringMVC控制网址->页面的映射)

3.1.1 在web.xml中配置SpringMVC使之生效,web.xml:

   <servlet>
        <servlet-name>spring-dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>spring-dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

3.1.2 在SpringMVC的配置文件servletname-servlet.xml中配置地址过滤规则,spring-dispatcher-servlet.xml:

    <context:component-scan base-package="web.quiz.controller" /><!-- 要扫描的包-->
    <mvc:annotation-driven />
    <!-- 解析网址,加前缀后缀,比如输入index时定位到/WEB-INF/jsp/index.jsp-->
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
    <!-- 不拦截静态资源,保证对js、css、jpg等的正常访问 -->
    <mvc:default-servlet-handler />

3.1.3 最后,在Controller中控制页面分发,QuizController.java:

    @RequestMapping(value = "vote", method = RequestMethod.GET)
    public String index() {
        return "vote";
    }

3.2 获取问卷内容。(Hibernate+Spring从数据库中读取问卷信息,发送到前端页面)

3.2.1 首先使用Hibernate关联对象与数据库。Hibernate有配置xml和注解两种实现方式,本文使用注解。Person.java:

//@javax.persistence.Entity注解一个实体Bean。数据表中的行对应实例,列对应实例的属性
    @Entity
    public class Person {
        @Id   //必须使用 @javax.persistence.Id 注解一个主键;
        private String id;             //编号
        private String name;            //姓名
    }

3.2.2 然后实现相应的DAO接口。PersonDAO.java、PersonDAOImpl.java:

    public interface PersonDAO {
        public List<Person> getAll();
    }
  
    @Repository //@Repository用于标注数据访问组件,即DAO组件;
    public class PersonDAOImpl implements PersonDAO{
    @Autowired //@Autowired可以对成员变量、方法和构造函数进行标注,来完成自动装配。默认按照类型进行装配。
    private SessionFactory sessionFactory;
      public List<Person> getAll() {
          Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Person.class);
          return criteria.list();
      }
  }

3.2.3 PersonDAOImpl中要用的sessionFactory怎么来?什么是SessionFactory:

SessionFactory接口负责初始化Hibernate。SessionFactory并不是轻量级的,因为一般情况下,一个项目通常只需要一个SessionFactory就够。

所以把SessionFactory放在Web初始化时候生成,使用Spring实现其自动装配。

首先在web.xml中配置Spring:

    <!--配置Spring-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:/META-INF/applicationContext.xml,
            classpath:/META-INF/spring-jdbc.xml
        </param-value>
    </context-param>

然后在Spring配置文件中配置Hibernate,spring-jdbc.xml:

       <!--自定义的hibernate.properties文件,下面${XXX}的内容来源-->
       <context:property-placeholder location="classpath:/META-INF/properties/hibernate.properties" />

       <!-- 使用C3P0数据源,MySQL数据库 -->
       <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
             destroy-method="close">
              <!-- MySQL5 -->
              <property name="driverClass" value="${driverClassName}"></property>
              <property name="jdbcUrl" value="${url}"></property>
              <property name="user" value="${username}"></property>
              <property name="password" value="${password}"></property>
       </bean
       <!-- session工厂 -->
       <bean id="sessionFactory"
             class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
              <property name="dataSource" ref="dataSource" />
              <property name="packagesToScan" value="web.quiz.model" />
              <property name="hibernateProperties">
                     <props>
                            <prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
                            <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
                            <prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
                     </props>
              </property>
       </bean>
        <!--hibernate的事务管理器-->
       <bean id="transactionManager"
             class="org.springframework.orm.hibernate4.HibernateTransactionManager">
              <property name="sessionFactory" ref="sessionFactory"></property>
       </bean>

在Spring的默认配置文件applicationContext.xml中配置扫描的包。这些包中有Spring注解为@Component的类,在使用注解配置的情况下,系统启动时会被自动扫描,并添加到bean工厂中去(省去了配置文件中写bean定义了)。applicationContext.xml:

    <context:component-scan base-package="web.quiz.service"/>
    <context:component-scan base-package="web.quiz.DAO"/>

至此,PersonDAOImpl中有了可用的sessionFactory,它的功能也能尽数实现了,如3.2.2中所示。

3.2.4 此时有了PersonDAO的实现,进一步封装成可直接用的服务。DBService.java 、DBServiceImpl.java:

public interface DBService{
    public List<Person> loadPersons();
}

@Service  //@Service用于标注业务层组件
@Transactional //@Transactional 可以作用于接口、接口方法、类以及类方法上。赋予其事务属性
public class DBServiceImpl implements DBService{
    @Autowired
    private PersonDAO personDAO;
    public List<Person> loadPersons(){
        return personDAO.getAll();
    }
}

至此,读写数据的功能有了。可以在Controller中调用服务读数据,QuizController.java:

@Controller
public class QuizController {
    @Resource  //与@Autowired等效,是JDK支持的注解,默认按照名称进行装配
    private DBService dbService;
    @PostConstruct  //使用@PostConstruct注释初始化方法。在Controller中,用@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器调用一次。
    private void initQuiz(){
        this.persons = dbService.loadPersons();
    }
}

3.2.5 把读取的内容发给前端JSP页面。本文使用了两种方法,一是传json回去,二是直接传对象回去。

方法一:传json到前端,用拼接字符串的方式生成页面。这种方法很笨,当时知识面太窄。不过用json传数据在做数据可视化时比较方便,所以也保留了这些代码。QuizController.java:

 @RequestMapping(value = "/loadPaper", method = RequestMethod.GET)
    @ResponseBody
    public byte[] loadPaper() throws IOException {
        ObjectMapper objectMapper = new ObjectMapper();
        String jsonString = objectMapper.writeValueAsString(this.quiz);
        byte[] b = jsonString.getBytes("UTF-8");        //解决传到前端后中文乱码问题
        return b;
    }

在前端用js解析json,生成页面。问卷页面vote.jsp:

function loadPage(){
            $.getJSON("loadPaper",function(data){  //获取问卷数据quiz,放入data中
                var names = data.names;
                 for (var i=0; i< names.length; i++){
                      var testDiv = '<div class="test">' + '<p class="name">' + (i+1) + ".&nbsp" + names[i] + ':</p>';
                      ...
                      $("form").append(testDiv);
                }
             }

方法二:借助ModelAndView对象,直接传对象到前端。配合JSP的特点,比较优雅高效。QuizController.java中:

//在方法的参数列表中添加形参 ModelMap map, spring 会自动创建ModelMap对象。
//然后调用map的put(key,value)或者addAttribute(key,value)将数据放入map中,spring会自动将数据存入request。
public ModelAndView check(HttpServletRequest request, HttpServletResponse response, ModelMap model){
  model.addAttribute("persons", persons);  //添加名为persons的对象
            return new ModelAndView("result");   //返回页面result.jsp
}

在前端使用JSTL <c:forEach>和EL表达式循环生成表格。显示成绩页面result.jsp:

        <c:forEach items="${persons}" var="person">
            <tr>
                <td>${person.id}</td>
                <td><a href="${person.id}/detail">${person.name}</a></td> <!--REST风格-->
                <td>${person.department}</td>
            </tr>
        </c:forEach>

(注意:使用EL表达式时,需加入<%@page isELIgnored="false" %>)

这里把每个人名作为一个超链接,点进去显示其测评结果。${person.id}/detail会变成如1/detail这样的地址。该地址在QuizController.java中这么解析:

@RequestMapping(value = "/{id}/detail", method = RequestMethod.GET)
    public ModelAndView detail(HttpServletRequest request, ModelMap model, @PathVariable String id) {
        Result result = dbService.getResultByID(id);
        String name = result.getName();
        model.addAttribute("name", name);
        return new ModelAndView("detail");
    }

3.2.6 扩展知识
上述传参方式叫REST( Representational State Transfer)风格。
关于SpringMVC应用REST风格,参考:Spring MVC 实现增删改查 - CSDN博客
关于其它URL传参方式,参考: SpringMVC之@RequestParam @PathVariable对比
关于SpringMVC中各种常用传值方法,参考:springMVC 将controller中数据传递到jsp页面

3.3 填写问卷,点提交。(Controller接收前端表单,结果写入数据库)

主要是前端传值到后端的问题。
在vote.jsp中:

<form method="POST" action="submit">
...
</form>

在QuizController.java中:

 @RequestMapping(value = "/submit", method = RequestMethod.POST)
    public String submit(HttpServletRequest request, HttpServletResponse response) {
        Enumeration<String> enu = request.getParameterNames();        //获得表单中所有值
        while (enu.hasMoreElements()) {
            String paraName = (String) enu.nextElement();
            String value = request.getParameter(paraName);   //按照表单中的顺序,一个个接收值
            ...
            dbService.saveOrUpdateResult(result); //将成绩写入数据库中
        }
    }

3.4 管理员登录,查看结果。(身份验证后,JSP展示结果)

提供管理员登录页面login.jsp,登录后进入参评人列表页面result.jsp;点击参评人超链接,进入详细成绩页面detail.jsp,统计分析个人得票结果。

到这里已经没有什么难度,不再写了。


一点心得

1. hibernate如何将数组成员对应到数据库

对于Question对象,成员id、title都能自动对应数据库中表的一列,而options作为一个List就带来很多麻烦。

解决方法:把List<String>拼接成一个String保存,当使用时,按照约定规则拆分即可:

    String[] options = questions[i].options.split("#"); //自行约定,这里用#分隔字符

成员变为:

    private String options;

完美!


结语

终于搭好Java SSH这个架子,实现了基本功能,但不想再继续写了。Web技术迭代太快,后面去尝试下其它技术。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,745评论 6 342
  • 1、谈谈你对Struts的理解。 答: 1.struts是一个按MVC模式设计的Web层框架,其实它就是一个大大的...
    慕容小伟阅读 2,749评论 0 13
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 人的这一路,终究是要自己走的,无论是开心,快乐,痛苦,悲哀,绝望,失望,哭泣,最终都是要自己承担的,或许你开心了可...
    坠金钗520阅读 246评论 0 0
  • 习惯了每周假期只有半天 习惯了每天熬夜熬到十一点 习惯了在题海里拼命奋战 习惯了做题作到自己狼狈不堪 只为了那一串...
    鸣兮阅读 251评论 4 8