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

我在大三实习的时候曾经在一家公司使用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
@Transactional
public 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);
	}

	@Override
	public 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;
	}

	@Override
	public Cluster getClusterByID(int cluster_id) {
		Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);
		return c;
	}

	@Override
	public void updateCluster(Cluster c) {
		getSession().saveOrUpdate(c);
	}

	@Override
	public void deleteCluster(int cluster_id) {
		Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);
		getSession().delete(c);
	}

	@Override
	public 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);
	}

	@Override
	public 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;
	}

	@Override
	public Cluster getClusterByID(int cluster_id) {
		Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);
		return c;
	}

	@Override
	public void updateCluster(Cluster c) {
		getSession().saveOrUpdate(c);
	}

	@Override
	public void deleteCluster(int cluster_id) {
		Cluster c = (Cluster)getSession().get(Cluster.class, cluster_id);
		getSession().delete(c);
	}

	@Override
	public 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。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值