使用Spring + Struts + Hibernate开发网站 -- 问题记录

来源:互联网 发布:nike beacon 知乎 编辑:程序博客网 时间:2024/06/01 22:40

我在大三实习的时候曾经在一家公司使用PHP开发网站,最近在另外一家公司实习,采用的语言是Java。虽然大学本科的时候学校做项目用java比较多,但是j2ee和android并没有学习多少。只是用Java写写算法题和几个桌面端程序。这篇文章算是对使用SSH开发的一个问题记录跟总结吧。

项目使用的软件版本是:hibernate 4.1.3,spring 3.1.1 release, struts 2.3.3

1 web.xml

<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" id="WebApp_9" version="2.4">  <display-name>docmanager</display-name>  <welcome-file-list>    <welcome-file>/admin/login.jsp</welcome-file>  </welcome-file-list>  <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>  <listener>    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  </listener>  <context-param>    <param-name>contextConfigLocation</param-name>    <param-value>/WEB-INF/applicationContext.xml</param-value>  </context-param></web-app>

配置了网站首页、struts过滤器、Spring集成。

2 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"     xmlns:aop="http://www.springframework.org/schema/aop"     xmlns:tx="http://www.springframework.org/schema/tx"     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd"><bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"><!-- 指定连接数据库的驱动 --><property name="driverClass" value="com.mysql.jdbc.Driver"/><!-- 指定连接数据库的URL --><property name="jdbcUrl" value="jdbc:mysql://xxx:3306/docmanager"/><!-- 指定连接数据库的用户名 --><property name="user" value="root"/><!-- 指定连接数据库的密码 --><property name="password" value="123456"/><!-- 指定连接数据库连接池的最大连接数 --><property name="maxPoolSize" value="20"/><!-- 指定连接数据库连接池的最小连接数 --><property name="minPoolSize" value="1"/><!-- 指定连接数据库连接池的初始化连接数 --><property name="initialPoolSize" value="1"/><!-- 指定连接数据库连接池的连接的最大空闲时间 --><property name="maxIdleTime" value="20"/></bean>    <!--定义了Hibernate的SessionFactory -->    <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">        <property name="dataSource" ref="dataSource"/>        <property name="packagesToScan" value="com.intel.bigbench.model" />        <property name="hibernateProperties">            <props>                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>                <prop key="show_sql">true</prop>                <prop key="hibernate.hbm2ddl.auto">update</prop>                <prop key="hibernate.jdbc.batch_size">20</prop>                <prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop>             </props>        </property>    </bean>    <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">        <property name="sessionFactory" ref="sessionFactory"/>    </bean><tx:annotation-driven/>     <bean id="transactionInterceptor" class="org.springframework.transaction.interceptor.TransactionInterceptor">    <!--  事务拦截器bean需要依赖注入一个事务管理器 -->        <property name="transactionManager" ref="transactionManager"/>    <property name="transactionAttributes">    <!--  下面定义事务传播属性-->    <props>    <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>    <prop key="*">PROPAGATION_REQUIRED</prop>    </props>    </property></bean>    <!-- 定义BeanNameAutoProxyCreator-->    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">    <!--  指定对满足哪些bean name的bean自动生成业务代理 -->    <property name="beanNames">            <!--  下面是所有需要自动创建事务代理的bean-->            <list>                <value>userService</value>            </list>            <!--  此处可增加其他需要自动创建事务代理的bean-->    </property>        <!--  下面定义BeanNameAutoProxyCreator所需的事务拦截器-->        <property name="interceptorNames">            <list>                <!-- 此处可增加其他新的Interceptor -->                <value>transactionInterceptor</value>             </list>        </property>    </bean><!-- Dao --><bean id="commonDao" class="com.intel.bigbench.dao.CommonDao"><property name="sessionFactory" ref="sessionFactory"/></bean><bean id="userDao" class="com.intel.bigbench.dao.impl.UserDaoImpl" parent="commonDao"></bean><bean id="clusterDao" class="com.intel.bigbench.dao.impl.ClusterDaoImpl" parent="commonDao"></bean><!-- Service --><bean id="userService" class="com.intel.bigbench.service.impl.UserServiceImpl" autowire="byName"></bean><bean id="clusterService" class="com.intel.bigbench.service.impl.ClusterServiceImpl" autowire="byName"></bean><!-- Action --><bean id="UserAction" class="com.intel.bigbench.action.UserAction" autowire="byName"></bean><bean id="SettingAction" class="com.intel.bigbench.action.SettingAction" autowire="byName"></bean><bean id="ClusterAction" class="com.intel.bigbench.action.ClusterAction" autowire="byName"></bean><bean id="FileAction" class="com.intel.bigbench.action.FileAction"></bean><bean id="FileUtil" class="com.intel.bigbench.util.FileUtil"><property name="uploadPath" value="C:\\upload" /></bean></beans>

几点说明:

  • sessionFactory中有一个配置,<prop key="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</prop> ,另外还有一个<tx:annotation-driven/> 配置以及<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">       <property name="sessionFactory" ref="sessionFactory"/>   </bean>,是为了解决no session for current thread exception。为了解决这个exception,还需要在相关service的类中加入注释,例如在我的项目中有一个ClusterServiceImpl。
@Service@Transactionalpublic class ClusterServiceImpl implements ClusterService {private ClusterDao clusterDao;        。。。}
如果说在类名之前加入了这两个注释,那么这个类里面的每一个方法只要是使用到hibernate的数据库操作,都会是一个独立的transaction。session context会自动为每一个transaction新建一个session,也就不会出现no session for current thread这个exception(个人理解,不是很深刻)。这里需要注意的是这两个注释应该加在哪个类上,应该是加在service中,service中引用了Dao对象,而Dao对象是继承了ActionSupport的。
  • 采用Spring管理Dao对象的sessionFactory装配。由于每个Dao都会有一个sessionFactory,于是我将其抽取出来成为一个独立的类,叫做CommonDao,代码如下
import org.hibernate.Session;import org.hibernate.SessionFactory;public class CommonDao {protected SessionFactory sessionFactory;public void setSessionFactory(SessionFactory sessionFactory) {this.sessionFactory = sessionFactory;}public Session getSession(){return this.sessionFactory.getCurrentSession();}}
而让其他的Dao实现继承这个类,例如

import java.util.List;import org.hibernate.Query;import com.intel.bigbench.dao.ClusterDao;import com.intel.bigbench.dao.CommonDao;import com.intel.bigbench.model.Cluster;import com.intel.bigbench.model.ClusterNode;import com.intel.bigbench.model.User;public class ClusterDaoImpl extends CommonDao implements ClusterDao {public Integer save(Cluster cluster){return (Integer) getSession().save(cluster);}@Overridepublic List<Cluster> getAllByUid(int uid) {Query query = getSession().createQuery("from Cluster c where c.user_id='" + uid + "'");List list = query.list();if(list != null && list.size()>=1){return list;}return null;}@Overridepublic Cluster getClusterByID(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);return c;}@Overridepublic void updateCluster(Cluster c) {getSession().saveOrUpdate(c);}@Overridepublic void deleteCluster(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);getSession().delete(c);}@Overridepublic void deleteClusterNode(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);int size = c.getNodeList().size();for(int i = 0; i < size; i++){ClusterNode n = c.getNodeList().remove(c.getNodeList().size() - 1);n.setCluster(null);getSession().delete(n);}}}
而在applicationContext.xml里就像这样:
<bean id="commonDao" class="com.intel.bigbench.dao.CommonDao"><property name="sessionFactory" ref="sessionFactory"/></bean><bean id="clusterDao" class="com.intel.bigbench.dao.impl.ClusterDaoImpl" parent="commonDao"></bean>
加入了parent="commonDao",这样就会在装配的时候继承父类的装配实现。这里Spring进行装配,我不知道是哪一种方法。一是先实例化CommonDao,然后装配属性,被子类继承父类的成员变量,还是直接创建子类对象,调用父类的set方法进行装配而不是继承父类的成员变量。java对象实例化是先调用父类构造函数,再调用子类构造函数。但是Spring注入是哪种,是我比较疑惑的地方。总之,这样话就省去了为每一个Dao对象配置sessionFactory的麻烦。
  • 使用自动装配。例如:
    <bean id="userService" class="com.intel.bigbench.service.impl.UserServiceImpl" autowire="byName"></bean>
    只要userService这个bean里面有setXXX方法,而xml里恰好有id为xXX的bean,Spring就会自动装配。比如userService里有setUserDao()方法,而恰好配置的bean里有id为userDao的bean,于是就会自动装配。这样省去了很多繁琐的配置。
3  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>      <constant name="struts.i18n.encoding" value="UTF-8"></constant>       <constant name="struts.multipart.saveDir" value="/tmp"/>      <constant name="struts.multipart.maxSize" value="104857600"></constant>       <package name="struts2" namespace="/" extends="json-default">          <action name="login" class="UserAction">              <result name="success">/admin/statics.jsp</result>              <result name="input">/admin/login.jsp</result>          </action>                    <action name="upload" class="FileAction">              <result name="success">/admin/statics.jsp</result>          </action>                         <!-- SettingAction -->          <action name="settingAction" class="SettingAction">              <result name="success">/admin/admin.jsp</result>                 </action>                    <action name="findAllUser" class="SettingAction" method="findAllUser">              <result name="findAllUser" type="json">                 <param name="includeProperties">userList.*</param>              </result>                       </action>                    <action name="addUser" class="SettingAction" method="addUser">              <result name="addUser" type="json">                  <param name="includeProperties">userList.*</param>                            </result>                       </action>          <action name="addCluster" class="ClusterAction" method="addCluster">              <result name="addCluster" type="json">                  <param name="includeProperties">clusterList.*</param>                            </result>                 </action>          <action name="uploadConfFile" class="ClusterAction" method="parseConf">              <result name="parseConf" type="json">                  <param name="includeProperties">nl.*</param>                  <param name="excludeProperties">nl\[\d+\].cluster</param >                           </result>                 </action>      </package>  </struts>
几点说明:
  • structs.xml主要用来对action返回结果的处理,包括两种,一种是跳转,一种是返回json。跳转就是根据action方法返回的字符串来进行判断跳转到那个jsp。返回json则是多应用于ajax应用。对于返回json的方法,有两个属性来控制返回结果includeProperties跟excludeProperties。中间的是正则表达式。这个正则表达式匹配的是什么内容呢?通过网上搜索,我形成了一些个人的基本理解:匹配的是一个字符串,这个字符串相当于一个定位符,或者导航链,struts有一个内部类型转换器(OGNL),能够将对象、List转换为字符串,还可以将对象的特定属性转换为字符串。怎么转换呢?需要一个导航链,也就是我们配置文件中匹配的正则表达式。例如,user.username,list[0].username,前者是普通对象,后者是列表。<param name="excludeProperties">nl\[\d+\].cluster</param >这里的nl\[\d+\].cluster会匹配nl[0].cluster, nl[1].cluster,... 实际上就是将列表的部分属性给排除了。另外,nl其实是action的一个成员变量,在action执行具体方法时被赋值。
  • action里method属性。可以设置methon属性来决定让action执行哪一种方法。如果不适用method,会默认为execute方法。以前不知道有这个属性,结果一个action只能处理一个请求。如果在execute里根据请求的参数不同,例如cmd="add" 或者cmd="delete"来判断处理流程又显得不雅观,因此使用method这个属性指定处理的方法,就显得比较不错。
4 hibernate一对多映射
话不多少,直接上代码。
Cluster.java
import java.sql.Timestamp;import java.util.List;import javax.persistence.CascadeType;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.OneToMany;import javax.persistence.Table;@Entity@Table(name="cluster")public class Cluster {        private Integer id;        private List<ClusterNode> nodeList;        @OneToMany(mappedBy="cluster", fetch=FetchType.EAGER, cascade = {CascadeType.ALL}) public List<ClusterNode> getNodeList() {return nodeList;}public void setNodeList(List<ClusterNode> nodeList) {this.nodeList = nodeList;}        @Id@GeneratedValue(strategy = GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}}

ClusterNode.java
import javax.persistence.CascadeType;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.ManyToOne;import javax.persistence.Table;@Entity@Table(name="cluster_node")public class ClusterNode {private Integer id;private String hostname;private Cluster cluster;@Id@GeneratedValue(strategy = GenerationType.AUTO)public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}@ManyToOne(fetch=FetchType.EAGER)        @JoinColumn(name="cluster_id")public Cluster getCluster() {return cluster;}public void setCluster(Cluster cluster) {this.cluster = cluster;}public String getHostname() {return hostname;}public void setHostname(String hostname) {this.hostname = hostname;}}

几点说明:
  • 一个cluster对应多个ClusterNode, 在使用hibernate映射的时候,Cluster里有List, 而每一个clusterNode都会有一个他所属的cluster。这里有两端:一端跟多端。一端就是Cluster端,多端就是ClusterNode端。在写hibernate程序的时候,我们要有一种思想,那就是通过操控对象来操控数据库。这种思想一定要牢记,尽量排除通过sql语言来操控数据库的想法。在一端,有这么一个代码段:
        @OneToMany(mappedBy="cluster", fetch=FetchType.EAGER, cascade = {CascadeType.ALL}) public List<ClusterNode> getNodeList() {return nodeList;}
@oneToMany, 是一个注释,放在一端,类似的,在多端有@manyToOne. 后面括号里是属性。mappedBy字面理解就是由。。。映射而来, 我们看到这个方法返回的是List<ClusterNode>, mappedBy就是由ClusterNode.cluster映射而来,mappedBy后面的这个字符串一定要是ClusterNode里的成员变量名,而且这个成员变量名必须是Cluster类型。fetch=FetchType.EAGER是说加载方式是延迟还是立即。如果不是使用立即加载,好像会使用默认的延迟加载,即proxy。就是说会创建一个代理,等到需要使用对象的时候才会去查找数据库。这里容易发生一个exception,如果把这个对象延迟加载,等到使用的时候,hibernate的session关闭了,就会报一个异常,于是我这里采用立即加载。cascade = {CascadeType.ALL}说明我不管是增、删、改这个对象,都会进行级联更新。我们是通过操控对象来操控数据库,因此我们就主要控制Cluster端,通过Cluster的增删改来实现两个数据库表的增删改。

5 具体的Dao操作
import java.util.List;import org.hibernate.Query;import com.intel.bigbench.dao.ClusterDao;import com.intel.bigbench.dao.CommonDao;import com.intel.bigbench.model.Cluster;import com.intel.bigbench.model.ClusterNode;import com.intel.bigbench.model.User;public class ClusterDaoImpl extends CommonDao implements ClusterDao {public Integer save(Cluster cluster){return (Integer) getSession().save(cluster);}@Overridepublic List<Cluster> getAllByUid(int uid) {Query query = getSession().createQuery("from Cluster c where c.user_id='" + uid + "'");List list = query.list();if(list != null && list.size()>=1){return list;}return null;}@Overridepublic Cluster getClusterByID(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);return c;}@Overridepublic void updateCluster(Cluster c) {getSession().saveOrUpdate(c);}@Overridepublic void deleteCluster(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);getSession().delete(c);}@Overridepublic void deleteClusterNode(int cluster_id) {Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);int size = c.getNodeList().size();for(int i = 0; i < size; i++){ClusterNode n = c.getNodeList().remove(c.getNodeList().size() - 1);n.setCluster(null);getSession().delete(n);}}}
几点说明:
  • 删除从表,即ClusterNode的某一行时,在两端都要做处理,在Cluster端,需要list.remove,切除联系,在ClusterNode端,需要setCluster(null)切除联系,最后调用session.delete()来进行删除
  • 使用get代替使用load。hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。


0 0