我在大三实习的时候曾经在一家公司使用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的麻烦。
- 使用自动装配。例如:
只要userService这个bean里面有setXXX方法,而xml里恰好有id为xXX的bean,Spring就会自动装配。比如userService里有setUserDao()方法,而恰好配置的bean里有id为userDao的bean,于是就会自动装配。这样省去了很多繁琐的配置。<bean id="userService" class="com.intel.bigbench.service.impl.UserServiceImpl" autowire="byName"></bean>
<?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。