1. 基本需求
基于SSH框架的员工增删改查小案例的具体需求如下图所示:
2. 实现步骤
2.1 搭建SSH框架环境
第一步:添加Spring4框架,具体步骤如下:
- 加入Spring4框架所依赖的jar包;
- 配置web.xml文件使其能够在WEB应用中正常使用;
- 添加Spring的Bean配置。
第二步:添加Hibernate4框架,具体步骤如下:
- 加入Hibernate4所依赖的jar包;
- 在类路径下创建hibernate.cfg.xml配置文件,用于配置hibernate的基本属性;
- 建立所需要的持久化类及其对应的对象关系映射文件*.hbm.xml;
- 与Spring4进行整合,即添加C3P0数据源和MySQL数据库的驱动,并在Spring的Bean配置文件中配置数据源、SessionFactory和声明式事务;
- 配置成功后,启动当前WEB应用即可生成对应的数据表。
第三步:添加Struts2框架,具体步骤如下:
- 加入Struts2框架所依赖的jar包,若有重复则删除版本较低的jar包;
- 在web.xml文件中配置Struts2的过滤器;
- 添加Struts2的配置文件;
- 与Spring4进行整合,主要包括:
- 加入Struts2的Spring插件,即struts2-spring-plugin-2.3.31.jar;
- 在Spring的Bean配置文件中正常配置Action,要求其scope属性必须为prototype;
- 在Struts2的配置文件中配置Action,其class属性需指向该Action在IoC容器中所配置的id。
2.2 显示所有员工信息
EmployeeAction类中list()方法的核心代码如下所示:
/**
* 功能一:查询并显示所有员工信息
* 注意事项:
* 1). 在事务作用范围之外访问员工的部门名称可能会出现懒加载异常,采用迫切左外连接查询或使用OpenSessionInViewFilter来实现
* 2). 通过实现RequestAware接口来获取request,将获取的员工信息放入其属性中,即可在显示页面获取并显示
* @return
*/
public String list() {
/**
* System.out.println(employeeService.getAll()); //会出现懒加载异常
* 事务作用的EmployeeService的getAll()方法上,其执行完毕即提交事务,此时打印员工的部门信息即会出现异常
*/
request.put("EMPLOYEES", employeeService.getAll());
return "list";
}
2.3 删除当前员工信息
执行删除后需要将返回值设置为redirect类型,并重定向到emp-list;其中,删除时的提示信息和Ajax请求实现的核心代码如下所示:
<!-- Ajax的具体使用参看struts-2.3.31/docs/docs/ajax.html -->
<script type="text/javascript" src="scripts/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function() {
// 1). 弹出信息提示框
$(".delete").click(function() {
var name = $(this).next(":input").val();
var flag = confirm("确定要删除" + name + "的信息吗?");
if(flag) {
// 2). 删除员工的信息
var $tr = $(this).parent().parent();
// 发送删除请求
var url = this.href;
var args = {"times":new Date()}; //用于禁用缓存
$.post(url, args, function(data) {
if(data == "1") {
alert("恭喜您,删除成功!");
if($tr.siblings().length == 0) {
$tr.parent().parent().remove();
}
$tr.remove();
} else {
alert("对不起,删除失败!");
}
});
}
// 取消超链接的默认行为
return false;
});
});
</script>
2.4 添加新的员工信息
显示表单页面时,需要先查询出所有的部门信息;需要使用Struts2的ModelDriven和Preparable拦截器;也需要自定义转换器将时间字符串转为日期对象。其中,Ajax校验员工名的核心代码如下所示:
<script type="text/javascript" src="scripts/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function() {
$(":input[name=name]").change(function() {
var nameval = $(this).val();
nameval = $.trim(nameval);
$this = $(this);
if(nameval != "") {
$this.nextAll("font").remove();
var url = "emp-validateName";
var args = {"name":nameval, "time":new Date()};
$.post(url, args, function(data) {
if(data == "1") {
$this.after("<font color='green'>恭喜您,员工名可用!</font>");
} else if (data == "0") {
$this.after("<font color='red'>对不起,员工名不可用!</font>");
} else {
alert("服务器异常,请重试!");
}
});
} else {
alert("对不起,员工姓名不能为空!");
$(this).val("");
}
});
});
</script>
2.5 修改指定员工信息
需要回显当前员工的信息,并设置其员工名不可修改。
3. 具体实现
基于SSH框架的员工增删改查小案例的实现代码下载地址:http://download.youkuaiyun.com/download/bingbeichen/9790372。
代码目录结构:
EmployeeAction.java:
package com.qiaobc.ssh.actions;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Map;
import org.apache.struts2.interceptor.RequestAware;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
import com.qiaobc.ssh.entities.Employee;
import com.qiaobc.ssh.service.DepartmentService;
import com.qiaobc.ssh.service.EmployeeService;
public class EmployeeAction extends ActionSupport implements RequestAware, ModelDriven<Employee>, Preparable {
private static final long serialVersionUID = 1L;
private EmployeeService employeeService;
private DepartmentService departmentService;
public void setEmployeeService(EmployeeService employeeService) {
this.employeeService = employeeService;
}
public void setDepartmentService(DepartmentService departmentService) {
this.departmentService = departmentService;
}
/**
* 功能一:查询并显示所有员工信息
* 注意事项:
* 1). 在事务作用范围之外访问员工的部门名称可能会出现懒加载异常,采用迫切左外连接查询
* 2). 通过实现RequestAware接口来获取request,将获取的员工信息放入其属性中,即可在显示页面获取并显示
*
* @return
*/
public String list() {
/**
* System.out.println(employeeService.getAll()); //会出现懒加载异常
* 事务作用的EmployeeService的getAll()方法上,其执行完毕即提交事务,此时打印员工的部门信息即会出现异常
*/
request.put("EMPLOYEES", employeeService.getAll());
return "list";
}
private Map<String, Object> request;
@Override
public void setRequest(Map<String, Object> arg0) {
this.request = arg0;
}
/**
* 功能二-1:正常删除当前员工的信息
* 注意事项:根据员工id删除当前员工信息后需要重定向到emp-list,以防止删除操作的重复提交
*/
/*
* public String delete() { employeeService.delete(id); return "delete"; }
*/
private Integer id;
public void setId(Integer id) {
this.id = id;
}
private InputStream inputStream;
public InputStream getInputStream() {
return inputStream;
}
/**
* 功能二-2:使用Ajax删除当前员工的信息,需要使用jQuery弹出信息提示框
* Ajax的具体使用参见struts-2.3.31/docs/docs/ajax.html,与Struts2的文件下载类似
*/
public String delete() {
try {
employeeService.delete(id);
inputStream = new ByteArrayInputStream("1".getBytes("UTF-8"));
} catch (Exception e) {
e.printStackTrace();
try {
inputStream = new ByteArrayInputStream("0".getBytes("UTF-8"));
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
}
return "ajax-success";
}
/**
* 功能三:添加员工信息
* 1). 显示表单页面:需要先查询所有的部门信息
* 2). 使用Struts2的ModelDriven和Preparable拦截器
* 3). 需要将时间字符串转为日期对象
*/
public String input() {
// 获取所有的部门信息
request.put("DEPARTMENTS", departmentService.getDepartments());
return "input";
}
public void prepareInput() {
if(id != null) {
model = employeeService.get(id);
}
}
@Override
public void prepare() throws Exception {}
private Employee model;
@Override
public Employee getModel() {
return model;
}
public String save() {
if(id == null) {
model.setCreateTime(new Date());
}
employeeService.saveOrUpdate(model);
return "save";
}
/**
* 可以根据id来判断是从数据库获取model还是创建新的model
* @return
*/
public void prepareSave() {
if(id == null) {
model = new Employee();
} else {
model = employeeService.get(id);
}
}
private String name;
public void setName(String name) {
this.name = name;
}
public String validateName() throws UnsupportedEncodingException {
if(employeeService.validateName(name)) {
inputStream = new ByteArrayInputStream("1".getBytes("UTF-8"));
} else {
inputStream = new ByteArrayInputStream("0".getBytes("UTF-8"));
}
return "ajax-success";
}
}
SSHDateConverters.java:
package com.qiaobc.ssh.converters;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.struts2.util.StrutsTypeConverter;
public class SSHDateConverters extends StrutsTypeConverter {
private DateFormat dateFormat;
{
dateFormat = new SimpleDateFormat("yyyy-MM-dd");
}
@Override
public Object convertFromString(Map context, String[] values, Class toClass) {
System.out.println("convertFromString");
if(toClass == Date.class) {
try {
return dateFormat.parse(values[0]);
} catch (ParseException e) {
e.printStackTrace();
}
}
return values;
}
@Override
public String convertToString(Map context, Object o) {
System.out.println("convertToString");
if(o instanceof Date) {
return dateFormat.format((Date) o);
}
return null;
}
}
BaseDao.java:
package com.qiaobc.ssh.dao;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
public class BaseDao {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Session getSession() {
Session session = this.sessionFactory.getCurrentSession();
return session;
// return this.sessionFactory.getCurrentSession();
}
}
DepartmentDao.java:
package com.qiaobc.ssh.dao;
import java.util.List;
import com.qiaobc.ssh.entities.Department;
public class DepartmentDao extends BaseDao {
/**
* 查询所有的部门信息
*/
@SuppressWarnings("unchecked")
public List<Department> getDepartments() {
String hql = "FROM Department";
return getSession().createQuery(hql).list();
}
}
EmployeeDao.java:
package com.qiaobc.ssh.dao;
import java.util.List;
import com.qiaobc.ssh.entities.Employee;
public class EmployeeDao extends BaseDao {
@SuppressWarnings("unchecked")
public List<Employee> getAll() {
// 使用迫切左外连接查询防止出现懒加载异常
String hql = "FROM Employee e LEFT JOIN FETCH e.dept";
return getSession().createQuery(hql).list();
}
public void delete(int id) {
String hql = "DELETE FROM Employee e WHERE e.id = ?";
getSession().createQuery(hql).setInteger(0, id).executeUpdate();
}
public void saveOrUpdate(Employee employee) {
getSession().saveOrUpdate(employee);
}
public Employee validateName(String name) {
String hql = "FROM Employee e WHERE e.name = ?";
return (Employee) getSession().createQuery(hql).setString(0, name).uniqueResult();
}
public Employee get(Integer id) {
return (Employee) getSession().get(Employee.class, id);
}
}
EmployeeService.java:
package com.qiaobc.ssh.service;
import java.util.List;
import com.qiaobc.ssh.dao.EmployeeDao;
import com.qiaobc.ssh.entities.Employee;
public class EmployeeService {
private EmployeeDao employeeDao;
public void setEmployeeDao(EmployeeDao employeeDao) {
this.employeeDao = employeeDao;
}
public List<Employee> getAll() {
List<Employee> emps = employeeDao.getAll();
return emps;
}
public void delete(int id) {
employeeDao.delete(id);
}
public void saveOrUpdate(Employee employee) {
employeeDao.saveOrUpdate(employee);
}
public boolean validateName(String name) {
return employeeDao.validateName(name) == null;
}
public Employee get(Integer id) {
return employeeDao.get(id);
}
}
applicationContext-beans.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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置Struts2的Action所对应的bean:设置其scope属性为prototype -->
<bean id="employeeAction" class="com.qiaobc.ssh.actions.EmployeeAction" scope="prototype">
<property name="employeeService" ref="employeeService"></property>
<property name="departmentService" ref="departmentService"></property>
</bean>
<bean id="employeeDao" class="com.qiaobc.ssh.dao.EmployeeDao">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="employeeService" class="com.qiaobc.ssh.service.EmployeeService">
<property name="employeeDao" ref="employeeDao"></property>
</bean>
<bean id="departmentDao" class="com.qiaobc.ssh.dao.DepartmentDao">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<bean id="departmentService" class="com.qiaobc.ssh.service.DepartmentService">
<property name="departmentDao" ref="departmentDao"></property>
</bean>
</beans>
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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<!-- 配置数据源:配置完后可测试其是否配置成功 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initialPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置Hibernate的SessionFactory实例:通过Spring提供的LocalSessionFactoryBean进行配置 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- 配置数据源属性 -->
<property name="dataSource" ref="dataSource"></property>
<!-- 配置Hibernate配置文件的位置及名称 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<!-- 配置Hibernate映射文件的位置及名称:可以使用通配符 -->
<property name="mappingLocations" value="classpath:com/qiaobc/ssh/entities/*.hbm.xml"></property>
</bean>
<!-- 配置声明式事务 -->
<!-- 1). 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 2). 配置事务属性 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 3). 配置事务切入点、关联事务属性 -->
<aop:config>
<aop:pointcut expression="execution(* com.qiaobc.ssh.service.*.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
db.properties:
jdbc.user=root
jdbc.password=root
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///spring
jdbc.initialPoolSize=5
jdbc.maxPoolSize=10
hibernate.cfg.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置Hibernate的基本属性:方言、SQL显示与格式化、数据表生成策略、二级缓存相关 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
struts.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="ssh-emps" namespace="/" extends="struts-default">
<!-- 定义新的拦截器栈,配置prepare拦截器栈的alwaysInvokePrepare属性为false -->
<interceptors>
<interceptor-stack name="sshEmpsStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 使用新配置的拦截器栈 -->
<default-interceptor-ref name="sshEmpsStack"></default-interceptor-ref>
<action name="emp-*" class="employeeAction" method="{1}">
<result name="list">/WEB-INF/views/emp-list.jsp</result>
<result name="delete" type="redirect">/emp-list</result>
<result name="ajax-success" type="stream">
<param name="contentType">text/html</param>
<param name="inputName">inputStream</param>
</result>
<result name="input">/WEB-INF/views/emp-input.jsp</result>
<result name="save" type="redirect">/emp-list</result>
</action>
</package>
</struts>
emp-list.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<script type="text/javascript" src="scripts/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function() {
// 1). 弹出信息提示框
$(".delete").click(function() {
var name = $(this).next(":input").val();
var flag = confirm("确定要删除" + name + "的信息吗?");
if(flag) {
// 2). 删除员工的信息
var $tr = $(this).parent().parent();
// 发送删除请求
var url = this.href;
var args = {"times":new Date()}; //用于禁用缓存
$.post(url, args, function(data) {
if(data == "1") {
alert("恭喜您,删除成功!");
if($tr.siblings().length == 0) {
$tr.parent().parent().remove();
}
$tr.remove();
} else {
alert("对不起,删除失败!");
}
});
}
// 取消超链接的默认行为
return false;
});
});
</script>
<body>
<s:debug></s:debug>
<br>
<s:if
test="#request.EMPLOYEES == null || #request.EMPLOYEES.size() == 0">
<h4>暂没有任何员工信息...</h4>
</s:if>
<s:else>
<table border="1" cellpadding="10" cellspacing="0">
<thead>
<tr>
<td>ID</td>
<td>NAME</td>
<td>EMAIL</td>
<td>BIRTHDAY</td>
<td>CREATETIME</td>
<td>DEPARTMENT</td>
<td>DELETE</td>
<td>EDIT</td>
</tr>
</thead>
<tbody>
<s:iterator value="#request.EMPLOYEES">
<tr>
<td>${id }</td>
<td>${name }</td>
<td>${email }</td>
<td>
<s:date name="birth" format="yyyy-MM-dd"/>
</td>
<td>
<s:date name="createTime" format="yyyy-MM-dd hh-mm-ss"/>
</td>
<td>${dept.name }</td>
<td>
<a href="emp-delete?id=${id }" class="delete">Delete</a>
<input type="hidden" value="${name }">
</td>
<td><a href="emp-input?id=${id }">Edit</a></td>
</tr>
</s:iterator>
</tbody>
</table>
</s:else>
</body>
</html>
emp-input.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<script type="text/javascript" src="scripts/jquery-1.7.2.js"></script>
<script type="text/javascript">
$(function() {
$(":input[name=name]").change(function() {
var nameval = $(this).val();
nameval = $.trim(nameval);
$this = $(this);
if(nameval != "") {
$this.nextAll("font").remove();
var url = "emp-validateName";
var args = {"name":nameval, "time":new Date()};
$.post(url, args, function(data) {
if(data == "1") {
$this.after("<font color='green'>恭喜您,员工名可用!</font>");
} else if (data == "0") {
$this.after("<font color='red'>对不起,员工名不可用!</font>");
} else {
alert("服务器异常,请重试!");
}
});
} else {
alert("对不起,员工姓名不能为空!");
$(this).val("");
}
});
});
</script>
<body>
<h4>Add New Employee:</h4>
<s:form action="emp-save" method="post">
<s:if test="id != null">
<s:textfield label="Name" name="name" disabled="true"></s:textfield>
<s:hidden name="id"></s:hidden>
<!-- 可以通过添加隐藏域的方式将未提交的name和createTime字段值提交到服务器
<s:hidden name="name"></s:hidden>
<s:hidden name="createTime"></s:hidden>
-->
</s:if>
<s:else>
<s:textfield label="Name" name="name"></s:textfield>
</s:else>
<s:textfield label="Email" name="email"></s:textfield>
<s:textfield label="Birth" name="birth"></s:textfield>
<s:select list="#request.DEPARTMENTS"
listKey="id" listValue="name"
label="Department" name="dept.id"></s:select>
<s:submit></s:submit>
</s:form>
</body>
</html>