struts2作为一个很传统的mvc框架,在一些项目中,仍然发挥着重要的作用,另外hibernate和spring的结合也是很常见的,这里介绍struts2+hibernate5+spring4的整合,因为struts2支持最高的spring是4版本,所以这里采用的是spring4。整合的思路比较简单,可以从struts2+spring整合,然后接着整合hibernate+spring。也可以反过来,这里我们先整合hibernate与spring,让系统产生一些数据。然后把struts2整合进来,就有了页面,这样,数据有了,页面就可以动起来了。
先给出一个动图,这就是我们最后整合的简单示例:
工程最后的结构图:
搭建maven工程,引入相关的依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.xxx</groupId>
<artifactId>ssh</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>ssh Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<struts2.version>2.5.16</struts2.version>
<spring.version>4.3.13.RELEASE</spring.version>
<slf4j.version>1.7.25</slf4j.version>
<hibernate.version>5.2.17.Final</hibernate.version>
<jackson.version>2.9.6</jackson.version>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-core</artifactId>
<version>${struts2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-convention-plugin</artifactId>
<version>${struts2.version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-taglibs</artifactId>
<version>4.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-spring-plugin</artifactId>
<version>${struts2.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<version>1.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
</dependencies>
<build>
<finalName>ssh</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.7.v20170914</version>
<configuration>
<webApp>
<contextPath>/${project.build.finalName}</contextPath>
</webApp>
<stopKey>CTRL+C</stopKey>
<stopPort>8999</stopPort>
<scanIntervalSeconds>10</scanIntervalSeconds>
<scanTargets>
<scanTarget>src/main/webapp/WEB-INF/webapp.xml</scanTarget>
</scanTargets>
</configuration>
</plugin>
</plugins>
</build>
</project>
项目中使用了jetty-maven-plugin插件,这样,我们最后启动项目直接右键pom.xml运行run as -> run configuration ,然后配置maven build->jetty run,就可以启动web项目了,而不用额外通过tomcat来启动。方便,快捷。
第一部分:
java代码部分:
BaseEntity.java
package com.xxx.ssh.web.domain;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
@MappedSuperclass
public class BaseEntity {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
protected Integer id;
@Column(name="createdate")
protected Date createDate;
@Column(name="modifydate")
protected Date modifyDate;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
}
public Date getModifyDate() {
return modifyDate;
}
public void setModifyDate(Date modifyDate) {
this.modifyDate = modifyDate;
}
}
User.java
package com.xxx.ssh.web.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
@Entity
@Table(name="xx_user")
public class User extends BaseEntity {
private String username;
private String password;
@Column(length=11)
private String mobile;
private int age;
private boolean status;
@Override
public String toString() {
return "User [username=" + username + ", age=" + age + ", status=" + status + ", id=" + id + "]";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
}
BaseDao.java
package com.xxx.ssh.web.dao;
import java.io.Serializable;
import java.util.List;
public interface BaseDao<T> {
T findById(Serializable key);
void save(T entity);
void update(T entity);
void delete(Serializable key);
List<T> findAll();
}
BaseDaoImpl.java
package com.xxx.ssh.web.dao.impl;
import java.io.Serializable;
import java.util.List;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.ssh.web.dao.BaseDao;
public abstract class BaseDaoImpl<T> implements BaseDao<T> {
public abstract Class<T> getEntityClass();
@Autowired
private SessionFactory sessionFactory;
public Session getSession() {
return sessionFactory.getCurrentSession();
}
@Override
public void save(T entity) {
getSession().persist(entity);
}
@Override
public void update(T entity) {
getSession().update(entity);
}
@Override
public T findById(Serializable key) {
return getSession().get(getEntityClass(), key);
}
@Override
public void delete(Serializable key) {
getSession().delete(findById(key));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public List<T> findAll() {
String hql = "from "+getEntityClass().getName();
Query query = getSession().createQuery(hql);
return query.list();
}
}
UserDao.java
package com.xxx.ssh.web.dao;
import com.xxx.ssh.web.domain.User;
public interface UserDao extends BaseDao<User> {
}
UserDaoImpl.java
package com.xxx.ssh.web.dao.impl;
import org.springframework.stereotype.Repository;
import com.xxx.ssh.web.dao.UserDao;
import com.xxx.ssh.web.domain.User;
@Repository
public class UserDaoImpl extends BaseDaoImpl<User> implements UserDao {
@Override
public Class<User> getEntityClass() {
return User.class;
}
}
UserService.java
package com.xxx.ssh.web.service;
import java.util.List;
import com.xxx.ssh.web.domain.User;
public interface UserService {
void save(User entity);
void update(User entity);
void delete(Integer id);
User findById(Integer id);
List<User> findAll();
}
UserServiceImpl.java
package com.xxx.ssh.web.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xxx.ssh.web.dao.UserDao;
import com.xxx.ssh.web.domain.User;
import com.xxx.ssh.web.service.UserService;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void save(User entity) {
userDao.save(entity);
}
@Override
public void update(User entity) {
userDao.update(entity);
}
@Override
public void delete(Integer id) {
userDao.delete(id);
}
@Override
public User findById(Integer id) {
return userDao.findById(id);
}
@Override
public List<User> findAll() {
return userDao.findAll();
}
}
MyEntityInterceptor.java
package com.xxx.ssh.web.config;
import java.io.Serializable;
import java.util.Date;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
public class MyEntityInterceptor extends EmptyInterceptor {
private static final long serialVersionUID = 1L;
private static final String CREATE_DATE="createDate";
private static final String MODIFY_DATE="modifyDate";
@Override
public boolean onFlushDirty(Object entity, Serializable id,
Object[] currentState, Object[] previousState,
String[] propertyNames, Type[] types) {
for(int i=0;i<propertyNames.length;i++){
if(MODIFY_DATE.equals(propertyNames[i])){
currentState[i] = new Date();
}
}
return true;
}
@Override
public boolean onSave(Object entity, Serializable id, Object[] state,
String[] propertyNames, Type[] types) {
for(int i=0;i<propertyNames.length;i++){
if(CREATE_DATE.equals(propertyNames[i]) || MODIFY_DATE.equals(propertyNames[i])){
state[i] = new Date();
}
}
return true;
}
}
MD5Utils.java
package com.xxx.ssh.web.util;
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Utils {
public static String md5(String source) {
return DigestUtils.md5Hex(source);
}
}
配置文件部分:
spring.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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<context:component-scan base-package="com.xxx.ssh.web" />
<import resource="spring-database.xml"/>
</beans>
spring-database.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:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="myEntityInterceptor" class="com.xxx.ssh.web.config.MyEntityInterceptor"/>
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="entityInterceptor" ref="myEntityInterceptor"></property>
<property name="hibernateProperties">
<value>
hibernate.dialect=${hibernate.dialect}
hibernate.show_sql=${hibernate.show_sql}
hibernate.format_sql=${hibernate.format_sql}
hibernate.hbm2ddl.auto=${hibernate.hbm2ddl.auto}
hibernate.cache.use_second_level_cache=false
hibernate.cache.use_query_cache=false
hibernate.jdbc.fetch_size=50
hibernate.jdbc.batch_size=50
hibernate.connection.release_mode=auto
hibernate.connection.autocommit=true
hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
</value>
</property>
<property name="packagesToScan" value="com.xxx.ssh.web.domain"></property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="transfer*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut expression="execution( * com.xxx.ssh.web.service.*.*(..))" id="tx-pointcut"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="tx-pointcut"/>
</aop:config>
</beans>
jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///ssh?useUnicode=true&useSSL=false&characterEncoding=UTF-8
jdbc.username=hadoop
jdbc.password=hadoop
hibernate.maxPoolSize=50
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
单元测试类:
package com.xxx.ssh.test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.xxx.ssh.web.domain.User;
import com.xxx.ssh.web.service.UserService;
import com.xxx.ssh.web.util.MD5Utils;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:spring.xml"})
public class UserTest {
@Autowired
private UserService userService;
@Test
public void save() {
User user = new User();
user.setUsername("hadoop");
user.setPassword(MD5Utils.md5("123456"));
user.setAge(22);
user.setMobile("13800138001");
user.setStatus(true);
userService.save(user);
}
@Test
public void update() {
User user = userService.findById(1);
user.setStatus(true);
userService.update(user);
}
}
利用save()方法,可以像数据库中增加几条数据,至此,第一部分整合完毕。
第二部分:
java代码部分:
UserAction.java
package com.xxx.ssh.web.action.admin;
import java.util.List;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.springframework.beans.factory.annotation.Autowired;
import com.xxx.ssh.web.domain.User;
import com.xxx.ssh.web.service.UserService;
@ParentPackage("ssh")
public class UserAction {
@Autowired
private UserService userService;
private List<User> users;
private Integer id;
private User user;
public String detail() {
System.out.println(id);
user = userService.findById(id);
return "detail";
}
public String list() {
users = userService.findAll();
return "list";
}
public void setUsers(List<User> users) {
this.users = users;
}
public List<User> getUsers() {
return users;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setUser(User user) {
this.user = user;
}
public User getUser() {
return user;
}
}
配置文件部分:
struts.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<struts>
<constant name="struts.devMode" value="true"/>
<constant name="struts.enable.DynamicMethodInvocation" value="true"/>
<constant name="struts.convention.action.mapAllMatches" value="true"/>
<constant name="struts.convention.package.locators" value="action"/>
<constant name="struts.objectFactory" value="spring"/>
<constant name="struts.convention.relative.result.types" value="freemarker"/>
<package name="ssh" extends="struts-default">
<global-allowed-methods>regex:.*</global-allowed-methods>
<action name="index">
<result type="freemarker">/index.html</result>
</action>
</package>
</struts>
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/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
<display-name>ssh</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring*.xml</param-value>
</context-param>
<!-- Bootstraps the root web application context before servlet initialization -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>ssh</filter-name>
<filter-class>org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ssh</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
页面部分:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ssh</title>
</head>
<body>
<h2>welcome to ssh.</h2>
<a href="admin/user!list.action">用户列表</a>
</body>
</html>
user_detail.ftl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户列表</title>
</head>
<body>
<h2>欢迎来到用户详情页</h2>
<h2>welcome you,${user.username}.</h2>
</body>
</html>
user_list.ftl
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用户列表</title>
<style type="text/css">
ul li span{display:inline-block;width:100px;}
</style>
</head>
<body>
<h2>欢迎来到用户列表页</h2>
<ul>
<li>
<span>id</span>
<span>用户名</span>
<span>电话</span>
<span>年龄</span>
<span>创建日期</span>
<span>状态</span>
<span>操作</span>
</li>
<#list users as user>
<li>
<span>${user.id}</span>
<span>${user.username}</span>
<span>${user.mobile}</span>
<span>${user.age}</span>
<span>${user.createDate}</span>
<span>${user.status?string("yes","no")}</span>
<span><a href="${base}/admin/user!detail.action?id=${user.id}">用户详情</a></span>
</li>
</#list>
</ul>
</body>
</html>
这里为什么index.html页面是html,而user_detail.ftl和user_list.ftl页面是freemarker,其实html页面完全可以作为freemarker模板的,但是ftl模板页面,在struts.xml配置文件中不用指定<result type="freemarker"></result>就可以,因为struts默认指定的模板就是freemarker。
至此,整合就基本结束了,运行项目,访问http://127.0.0.1:8080/ssh,就可以看到开始那个动图的效果了。
项目配置里面补充log4j2.xml配置文件,这样,我们启动项目,可以看到相关日志,另外,也可以看到sql打印信息,在开发阶段非常有意义。
<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss} [%-5p] %c %m%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="com.opensymphony.xwork2" level="warn"/>
<Logger name="org.apache.struts2" level="info"/>
<Root level="info">
<appender-ref ref="Console"/>
</Root>
</Loggers>
</Configuration>
打印的日志: