springMVC学习(S2SH调整)

springMVC学习(S2SH整合)

前面几篇博客学习了spring中IOC,和springAOP以及spring通过jdbc操作数据库的方法。我们的目标是学习springMVC,那么在正式学习springMVC之前呢,我还是想到先写一篇文章来讲解S2SH整合吧,这样大家简单了解了structs2,对于后面学习springMVC也是有很大好处的。
我们先来整合spring和hibernate吧,我这里新建一个web工程叫做ssh。

添加spring和hibernate的jar

将spring和hibernate需要的jar文件添加到WEB-INFO下的lib目录中。需要的jar文件如下:
hibernate3.jar
hibernate-jpa-2.0-api-1.0.0.Final.jar
antlr-2.7.6.jar
commons-collections-3.1.jar
dom4j-1.6.1.jar
javassist-3.12.0.GA.jar
jta-1.1.jar
slf4j-api-1.6.1.jar
spring.jar
commons-logging.jar
log4j-1.2.15.jar
mysql-connector-java-3.0.10-stable-bin.jar
commons-dbcp.jar
commons-pool.jar

创建实体类和映射文件

创建实体类和映射对象,这里我依然通过一个user实体来操作,因此我需要新建一个Userinfo实体类,如下:

    package com.test.entity;

import java.io.Serializable;

public class Userinfo implements Serializable {
    private int uid;
    private String uname;
    private String upass;

    public int getUid() {
        return uid;
    }
    public void setUid(int uid) {
        this.uid = uid;
    }
    public String getUname() {
        return uname;
    }
    public void setUname(String uname) {
        this.uname = uname;
    }
    public String getUpass() {
        return upass;
    }
    public void setUpass(String upass) {
        this.upass = upass;
    }
}

这里,由于UserInfo实体类,以后会在网络上传输,所以需要实现Serializable接口。然后需要创建对应的实体类映射,我们需要在实体类同样的包下,新建一个Userinfo.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC 
    "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
    "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
    <class name="com.test.entity.Userinfo" table="userinfo">
        <id name="uid" column="uid">
            <generator class="native"></generator>
        </id>
        <property name="uname"></property>
        <property name="upass"></property>
    </class>
</hibernate-mapping>  

这里class标签中的name的值就是需要映射的实体类的全类名,table的值就是数据库中该实体类对应的表的名称,有一个id,标签,就是该userinfo表所对应的主键,这里我们逐渐的生成测试采用”native”,该中方式可以根据不同的底层数据库采用不同的主键生成方式,说明一下genernator的属性吧。
在hibernate中genernator的属性一共有七种,我们来看几个常用的:
1.identity用于mysql数据库的递增,注意需要在mysql中建表的时候将对应的字段设置为auto_increment
2.sequence用于orecale数据库
3.native 跨数据库时候使用,屏蔽了底层的方言
4.assigned 用户自定义id
5.foreign 用于外键关联时候使用
6.increment 用于为long,short,int类型生成唯一的表示,在高并发下不能使用


表示实体类对应到表中的字段,name表示实体类中的属性,如果我们不为该property标签指定column=”“属性,那么对应的userinfo表中的字段名称和类中的属性名是相同的,如果指定了column=”“,比如 此时userinfo表中uname属性对应的字段名称就叫做”t_uname”

编写Dao及其实现类

编写dao以及实现类,用来操作数据库的(dao需要集成HibernateDaoSupport),首先编写一个UserinfoDao的接口:

    package com.test.dao;

import java.util.List;

import com.test.entity.Userinfo;

public interface UserinfoDao {
    public int addUserinfo(Userinfo user);
    public int updateUserinfo(Userinfo user);
    public int deleteUserinfo(int uid);
    public List<Userinfo> getAll();
    public Userinfo getUserinfoById(int uid);
}

可以看到,在该接口中定义了”增删改查”四个方法,然后编写UserinfoDao的实现类,UserinfoDaoImpl.java

    package com.test.dao.impl;

import java.util.List;

import org.springframework.dao.DataAccessException;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.test.dao.UserinfoDao;
import com.test.entity.Userinfo;

public class UserinfoDaoImpl extends HibernateDaoSupport implements UserinfoDao {

    public int addUserinfo(Userinfo user) {
        int count=0;
        try {
            this.getHibernateTemplate().save(user);
            count=1;
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return count;
    }

    public int deleteUserinfo(int uid) {
        int count=0;
        Userinfo user = getUserinfoById(uid);
        try {
            this.getHibernateTemplate().delete(user);
            count=1;
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return count;
    }

    public List<Userinfo> getAll() {
        return this.getHibernateTemplate().find("from Userinfo");
    }

    public Userinfo getUserinfoById(int uid) {
        return (Userinfo)this.getHibernateTemplate().get(Userinfo.class, uid);
    }

    public int updateUserinfo(Userinfo user) {
        int count=0;
        try {
            this.getHibernateTemplate().update(user);
            count=1;
        } catch (DataAccessException e) {
            e.printStackTrace();
        }
        return count;
    }
}

我们可以看到通过this.getHibernateTemplate();就可以得到HibernateTemplate 对象,然后通过该对象操作数据库是很方便的,HibernateTemplate 为我们提供的方法,我们只需要将对应的实体类,或该实体类的id传进去就可以操作数据库了,大大的提高了开发效率。
下面我写一个UserinfoBiz接口,并且让UserinfoBizImpl实现该接口同时在UserinfoBizImpl中声明一个UserInfoDao的属性,并且设置set方法,这样做是为了后边将UserInfoDao通过IOC注入给UserinfoBizImpl。

package com.test.biz;

import java.util.List;
import com.test.entity.Userinfo;

public interface UserinfoBiz {
    public int addUserinfo(Userinfo user);
    public int updateUserinfo(Userinfo user);
    public int deleteUserinfo(int uid);
    public List<Userinfo> getAll();
    public Userinfo getUserinfoById(int uid);
}
package com.test.biz.impl;

import java.util.List;

import com.test.biz.UserinfoBiz;
import com.test.dao.UserinfoDao;
import com.test.entity.Userinfo;

public class UserinfoBizImpl implements UserinfoBiz {
    private UserinfoDao userDao;

    public void setUserDao(UserinfoDao userDao) {
        this.userDao = userDao;
    }

    public int addUserinfo(Userinfo user) {
        return userDao.addUserinfo(user);
    }

    public int deleteUserinfo(int uid) {
        return userDao.deleteUserinfo(uid);
    }

    public List<Userinfo> getAll() {
        return userDao.getAll();
    }

    public Userinfo getUserinfoById(int uid) {
        return userDao.getUserinfoById(uid);
    }

    public int updateUserinfo(Userinfo user) {
        return userDao.updateUserinfo(user);
    }
}

创建spring容器,配置数据源

创建spring容器,配置数据源和sessionFactory,在src下新建spring的配置文件applicationContext.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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
            <!-- 驱动名称 -->
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <!-- url -->
            <property name="url" value="jdbc:mysql://localhost:3306/mydb?useUnicode=true&amp;characterEncoding=UTF-8"></property>
            <!-- 用户名 -->
            <property name="username" value="root"></property>
            <!-- 密码 -->
            <property name="password" value="root"></property>
        </bean>
</beans>
接下来配置sessionFactory,并将datasource注入:
    <!-- 配置sessionFactory,注入dataSource -->
        <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
            <!-- 注入dataSource -->
            <property name="dataSource" ref="dataSource"></property>
            <!-- 引入映射文件的位置 -->
            <property name="mappingResources">
                <list>
                    <value>com/test/entity/Userinfo.hbm.xml</value>
                </list>
            </property>
            <property name="hibernateProperties">
                <props>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
                </props>
            </property>
        </bean>

在org.springframework.orm.hibernate3.LocalSessionFactoryBean中有一个dataSource属性,就是将上面配置的dataSource注入给该属性。

<property name="mappingResources">
        <list>
            <value>com/test/entity/Userinfo.hbm.xml</value>
        </list>
</property>

mappResources,就是映射所有的实体类到数据库中的,注意这里是一个list,由于我们现在只有一个UserInfo,所以只写了这么一个,这里value的值,是实体类对应的映射文件的路径。

<property name="hibernateProperties">
        <props>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect
            </prop>
        </props>
</property>

hibernateProperties配置的是数据库的方言,即hibernate.dialect的值。这里是mysql的方言。hibernate.show_sql的值是true表示在程序运行期间是否打印sql语句,在开发调试期间最好打开,这样便于观察生成的sql语句是否正常。
接下来需要配置Dao,并将sessionFactory注入:

<!-- 配置Dao,并注入sessionFactory -->
<bean id="userDao" class="com.test.dao.impl.UserinfoDaoImpl">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean>

这里整个过程和spring的IOC配置是一样的,其实这里就是spring的ioc配置,最后需要配置Biz,并将Dao注入。

<!-- 配置Biz,并注入Dao -->
<bean id="userBiz" class="com.test.biz.impl.UserinfoBizImpl">
    <property name="userDao" ref="userDao"></property>
</bean>

这里,因为我在UserinfoBizImpl类中声明了userDao属性,并且为该属性设置了set方法,可以通过spring来为其赋值,这里我是通过ref引用上一步定义的userDao,可以发现我们整个过程都是一步一步通过IOC赋值的:
dataSource–>sessionFactory–>Dao–>Biz
此时,万事俱备,只欠东风,新建一个dataSource中配置的数据库mydb,然后新建一个userinfo表,该表有三个字段uid,uname,upass,注意建表的时候字段类型需要和实体类中对应哦。创建好了表以后,就可以编写测试类来测试我们的spring和hbiernate整合是否成功。

        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserinfoBiz userBiz = (UserinfoBiz)ac.getBean("userBiz");
        Userinfo user = new Userinfo();
        user.setUname("bb");
        user.setUpass("123");
        userBiz.addUserinfo(user);
        List<Userinfo> list = userBiz.getAll();
        for (Userinfo userinfo : list) {
            System.out.println(userinfo.getUid()+"\t"+userinfo.getUname()+"\t"+userinfo.getUpass());
        }

这里,我首先插入一条记录到userinfo表中,然后调用getAll()查询所有数据,打印如下:
Hibernate: insert into userinfo (uname, upass) values (?, ?)
Hibernate: select userinfo0_.uid as uid0_, userinfo0_.uname as uname0_, userinfo0_.upass as upass0_ from userinfo userinfo0_
1 bb 123
可以看到,这时查询所用的sql语句也一并给我们打印出来了,此时spring和hibernate整合就成功了,可是有时候在对数据库进行增删改查的时候,难免会需要使用事物,接下来我们就来配置hibernate事物。

<!-- 配置声明式事务 -->
<!-- 配置事务管理器,注入sessionFactory -->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean>     
    <!-- 配置事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED"/>
            <tx:method name="update*" propagation="REQUIRED"/>
            <tx:method name="delete*" propagation="REQUIRED"/>
            <tx:method name="*" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <!-- 定义切入点 -->
        <aop:pointcut expression="execution(* com.test.biz.*.*(..))" id="bizMethods"/>
        <!-- 将事务通知和切入点告知给通知者 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="bizMethods"/>
    </aop:config>

配置hibernate事物主要有一下几步:
1.配置事务管理器,注入sessionFactory
2.配置事务通知
3.定义切入点,将事务通知和切入点告知给通知者
在配置事物通知的时候,我将以add,update,delete开头的方法是必须开启事物的,另外一个”*”表示如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行,propagation有一下一些值:
REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启
SUPPORTS: 如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行
MANDATORY: 如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常
REQUIRES_NEW: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起
NOT_SUPPORTED: 总是非事务地执行,并挂起任何存在的事务
NEVER: 总是非事务地执行,如果存在一个活动事务,则抛出异常
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按 TransactionDefinition.PROPAGATION_REQUIRED 属性执行


这里我们的切入点是com.test.biz包下的所有方法,在说一遍吧:
第一个”*”表示方法的返回值类型
第二个”*”表示类名
第三个”*”表示方法名
第四个”..”表示方法中存在0个或者多个参数。
记住,添加aop所需要的jar:aspectjrt.jar,aspectjweaver.jar
此时hibernate的事物就配置好了,此时当我运行add,update,delete方法时都会开启事物,如果操作失败,事物将会回滚。接下来就是将struts2整合进来拉。
同样将strust2整合进来,

添加structs2的jar ##

首先需要添加struts2的jar文件:
commons-fileupload-1.2.2.jar
commons-io-2.0.1.jar
commons-lang3-3.1.jar
commons-logging-1.1.1.jar
freemarker-2.3.19.jar
javassist-3.11.0.GA.jar
ognl-3.0.5.jar
sqljdbc.jar
struts2-core-2.3.4.1.jar
struts2-dojo-plugin-2.3.4.1.jar
xwork-core-2.3.4.1.jar

同时需要添加struts2和spring整合的jar:struts2-spring-plugin-2.3.4.1.jar,有一些jar在spring和hibernate整合的时候已经添加过了,这里就不需要重复添加了,否则可能引起jar包冲突。

配置structs2的核心过滤器

在web.xml中配置struts2的核心过滤器和ContextLoaderListener

<?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">
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
    <!-- 加载spring的配置文件 -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- 指定spring配置文件的位置 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:applicationContext.xml</param-value>
    </context-param>
    <!-- OSIVFilter:解决hibernate懒加载问题 -->
    <filter>
        <filter-name>osivFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>osivFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- struts2核心过滤器 -->
    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

编写Action控制器

编写UserInfoAction,我们在编写action的时候,为该UserInfoAction声明一个UserInfoBiz属性,并且设置set方法,这样做是为了后边通过springIOC将UserInfoBiz注入给UserInfoAction,完美的实现了三层架构,注意:UserInfoAction必须继承自ActionSupport,如果我们需要用到HttpServletRequest,那么UserInfoAction需要声明一个HttpServletRequest request;属性,并且实现ServletRequestAware接口,实现了ServletRequestAware接口之后,需要重写一下方法:
public void setServletRequest(HttpServletRequest arg0) {
request=arg0;
}
这里是将arg0赋值给我们自己的属性。

package com.test.action;

import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.interceptor.ServletRequestAware;
import com.opensymphony.xwork2.ActionSupport;
import com.test.biz.UserinfoBiz;
import com.test.entity.Userinfo;

public class UserinfoAction extends ActionSupport implements ServletRequestAware{
    private UserinfoBiz userBiz;
    private HttpServletRequest request;
    private Userinfo user;

    public Userinfo getUser() {
        return user;
    }

    public void setUser(Userinfo user) {
        this.user = user;
    }

    public void setUserBiz(UserinfoBiz userBiz) {
        this.userBiz = userBiz;
    }

    public String getAll() throws Exception {
        List<Userinfo> list = userBiz.getAll();
        request.setAttribute("list", list);
        return "list";
    }

    public String doRegister() throws Exception {
        int result = userBiz.addUserinfo(user);
        if(result>0){
            return this.SUCCESS;
        }
        return this.ERROR;
    }

    public String doDelete() throws Exception {
        int result = userBiz.deleteUserinfo(user.getUid());
        if(result>0){
            return this.SUCCESS;
        }
        return this.ERROR;
    }

    public String doDetail() throws Exception {
        Userinfo userinfo = userBiz.getUserinfoById(user.getUid());
        request.setAttribute("userinfo", userinfo);
        return "detail";
    }
    public String doUpdate() throws Exception {
        int result = userBiz.updateUserinfo(user);
        if(result>0){
            return this.SUCCESS;
        }
        return this.ERROR;
    }   
    public void setServletRequest(HttpServletRequest arg0) {
        request=arg0;
    }
}

这里我们每个方法都返回的是一个string类型的数据,这是因为structs2会根据该返回值来跳转到对应的界面。如果需要将数据传递到jsp页面来显示的话,可以使用request.setAttribute(“list”, list);这样的形式来传递。其他的没什么可说的了。

配置Action并为action设置biz

在spring的配置文件中配置action并设置biz

<!-- 配置Action,并注入Biz -->
<bean id="userAction" class="com.test.action.UserinfoAction">
    <property name="userBiz" ref="userBiz"></property>
</bean>

这里我们是将structs2的action交给spring来管理。

配置structs2的配置文件

配置structs2的配置文件structs.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
    "http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
    <package name="sshDemo" extends="struts-default">
        <!-- class="伪类(spring配置文件中action的id)" -->
        <action name="user-*" class="userAction" method="{1}">
            <result name="list">/list.jsp</result>
            <result name="success" type="redirectAction">user-getAll</result>
            <result name="error">/error.jsp</result>
            <result name="detail">/detail.jsp</result>
        </action>
    </package>
</struts>   

这里”package”标签的名称可以随便起,但是必须继承自”struts-default”,action标签中的class是spring中配置的id,因为我们完全将action交给spring来处理了,我可看到我在这里利用”*”通配符和method=”{1}”来匹配,什么意思呢?举个栗子,比如我传入的是”http://localhost:8080/xiangmuming/user-add.action“,此时将会执行UserinfoAction.java类中的add方法,如果该方法返回”list”这个字符串,那么此时系统将会跳转到根目录下的”list.jsp”页面。
下面是时候编写我们的页面了。在web.xml中配置我的项目的默认主页面是index.jsp,如下:

<welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
</welcome-file-list>

我在index.jsp中创建一个连接,通过该连接可以查询所有的数据:

<a href="user-getAll.action">用户列表</a>

可以看到,”用户列表”会执行到UserInfoAction.java中的getAll方法,通过返回值和structs.xml分析,此方法会跳转到list.jsp,下面是list.jsp的代码:

<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags"  prefix="s"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <body>
     <table width="70%" align="center" border="1">
        <tr>
            <td>编号</td><td>用户名</td><td>密码</td><td>操作</td>
        </tr>
        <s:if test="#request.list.size==0">
        <tr>
            <td colspan="4">暂无用户信息!</td>
        </tr>
        </s:if>
        <s:else>
            <s:iterator value="#request.list" var="user">
              <tr>
                <td><s:property value="#user.uid"/> </td>
                <td><s:property value="#user.uname"/></td>
                <td><s:property value="#user.upass"/></td>
                <td>
                <a href="user-doDelete.action?user.uid=<s:property value="#user.uid"/>">删除 </a>
                <a href="user-doDetail.action?user.uid=<s:property value="#user.uid"/>">修改</a></td>
               </tr>
            </s:iterator>
        </s:else>
     </table>
  </body>
</html>

在list.jsp中使用了structs2的标签,因此需要导入struct2的标签”<%@ taglib uri=”/struts-tags” prefix=”s”%>”
在structs2标签中有和以及等标签。

<s:if test="#request.list.size==0">
     <tr>
        <td colspan="4">暂无用户信息!</td>
     </tr>
</s:if>

上面的if表示如果request中存在list的长度==0的情况,如果request中的list是有数据的:

 <s:else>
            <s:iterator value="#request.list" var="user">
              <tr>
                <td><s:property value="#user.uid"/> </td>
                <td><s:property value="#user.uname"/></td>
                <td><s:property value="#user.upass"/></td>
                <td>
                <a href="user-doDelete.action?user.uid=<s:property value="#user.uid"/>">删除 </a>
                <a href="user-doDetail.action?user.uid=<s:property value="#user.uid"/>">修改</a></td>
               </tr>
            </s:iterator>
</s:else>

通过标签来遍历list集合,这里和forech循环比较相似。user.uid,user.uname,user.upass是UserInfo中的属性。可以看到当我点击删除的时候,会跳转到UserInfoAction中的doDelete方法,删除的连接是跳转到doDetail方法。
运行截图:
springMVC学习(S2SH调整)


源码下载