需求:整合ssh框架做一个保存用户的业务,业务比较简单,重在ssh框架整合。
创建数据库和表
CREATE DATABASE ssh01;
USE DATABASE;
表由Hibernate创建,可以看配置是否成功
一:导入jar包
-
Hibernate需要jar
Hibernate基本jar mysql驱动 c3p0连接池 日志包 jpa
- Struts需要jar
Struts2基本jar -
Spring需要jar
Ioc(6个):beans,context,expression,core+两个日志 Aop(4个): spring-aop-4.2.4.RELEASE spring整合aspect aspectj:com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar aop联盟:com.springsource.org.aopalliance-1.0.0.jar spring声明式事务: spring-jdbc-4.2.4.RELEASE.jar spring-tx-4.2.4.RELEASE.jar
-
Spring整合web
spring-web-4.2.4.RELEASE.jar
-
Spring整合Hibernate
spring-orm-4.2.4.RELEASE.jar
-
Spring整合Struts2
struts2-spring-plugin-2.3.24.jar **注意** Spring整合Struts2包先不要导入,因为如果导入在项目启动的时候, 会在ServetContext中查找spring工厂,会报错,抛出下面异常
You might need to add the following to web.xml: <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> 17:46:11,396 ERROR Dispatcher:42 - Dispatcher initialization failed java.lang.NullPointerException
在配置ContextLoaderListener监听器在项目启动的时候创建spring容器的时候导入
最后:去除重复的jar struts2基本Jar和Hibernate基本Jar中都有
javassist-3.18.1-GA.jar,删除一个低版本的,否则在使用的时候会出现问题。
二:需要的配置文件
-
Hibernate需要的配置文件
Hibernate.cfg.xml:src路径下
<hibernate-configuration> <session-factory> <!-- 必要的配置信息:连接数据库的基本参数 --> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql:///ssh01</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <!-- Hibernate的属性 --> <!-- Hibernate的方言:作用,根据配置的方言生成相应的SQL语句 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <!-- Hibernate显示SQL语句: --> <property name="hibernate.show_sql">true</property> <!-- Hibernate格式化SQL语句: --> <property name="hibernate.format_sql">true</property> <!-- Hibernate的hbm2ddl(数据定义语言:create drop alter ...)属性 --> <property name="hibernate.hbm2ddl.auto">update</property> <!-- 配置C3P0连接池 --> <property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property> <!-- Hibernate加载映射 --> <mapping resource="com/itheima/domain/Customer.hbm.xml"/> </session-factory> </hibernate-configuration>
jdbc.properties:(对数据库参数的封装) src下
jdbc.driver=com.mysql.jdbc.Driver; jdbc.url=jdbc:mysql://localhost:3306/ssh01 jdbc.username=root jdbc.password=root
log4J.properties日志文件 src下
Customer.hbm.xml 需要保存客户实体的映射文件
<hibernate-mapping package="com.itheima.domain"> <class name="Customer" table="cst_customer"> <!-- 数据库表中的字段名和实体类属性名相同的时候可以省略Column属性 --> <id name="cust_id"> <!-- 配置主键生成策略 --> <generator class="native"></generator> </id> <property name="cust_name"></property> <property name="cust_source"></property> <property name="cust_industry"></property> <property name="cust_level"></property> <property name="cust_phone"></property> <property name="cust_mobile"></property> </class> </hibernate-mapping>
-
Struts需要的配置文件
web.xml:配置Struts核心过滤器
<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>
struts2.xml:配置action 位置src下
<struts> <constant name="struts.devMode" value="true"></constant> <package name="ssh" extends="struts-default" namespace="/"> <action name="customer_*" class="com.itheima.web.action.CustomerAction" method="{1}"> <result name="success">/success.jsp</result> </action> </package> </struts>
- applicationContext.xml src下(待会配置)
三:创建service,dao,domain,action
-
创建Customer实体类,以及实体类对应的映射文件映射文件看上面)
伪代码(为属性提供get/set方法)
public class Customer implements Serializable{ private static final long serialVersionUID = 1L; private Long cust_id; private String cust_name; private String cust_source; private String cust_industry; private String cust_level; private String cust_phone; private String cust_mobile;
-
CustomerService,CustomerServiceImpl
将customerService对象交给spring容器管理 service需要调用dao层的方法,进行属性注入
public class CustomerServiceImpl implements CustomerService { //创建dao,并提供set方法,进行属性注入 private CustomerDao customerDao; public void setCustomerDao(CustomerDao customerDao) { this.customerDao = customerDao; } @Override public void save(Customer customer) { customerDao.save(customer); } }
-
创建CustomerDao,CustomerDaoImpl
将CustomerDao对象交给spring容器管public class CustomerDaoImpl implements CustomerDao { @Override public void save(Customer customer) { //获取session对象,来操作数据库 Configuration config = new Configuration().configure(); SessionFactory factory = config.buildSessionFactory(); Session session = factory.openSession(); Transaction tx = session.beginTransaction(); session.save(customer); tx.commit(); session.close(); } }
-
创建CustomerAction并在struts.xml中进行配置(struts.xml文件)
public class CustomerAction extends ActionSupport implements ModelDriven<Customer> { private static final long serialVersionUID = 1L; //使用ModelDriven模型驱动进行数据封装,必须手动来创建对象 private Customer customer = new Customer(); @Override public Customer getModel() { return customer; } /* * 创建CustomerService对象调用service层的方法 * 因为action是由struts2创建service交给spring容器管理所以不可以直接注入 */ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); CustomerService customerService = (CustomerService) context.getBean("customerService"); //保存用户的方法 public String save(){ customerService.save(customer); return SUCCESS; } }
-
在spring容器中管理CustomerService,CustomerDao
<!-- 配置dao --> <bean id="customerDao" class="com.itheima.dao.impl.CustomerDaoImpl"></bean> <!-- 配置service并注入customerDao属性 --> <bean id="customerService" class="com.itheima.service.impl.CustomerServiceImpl"> <property name="customerDao" ref="customerDao"></property> </bean>
-
创建save.jsp
<form action="${pageContext.request.contextPath}/customer_save.action" method="post"> 客户名称:<input type="text" name="cust_name"><br/> 客户等级:<input type="text" name="cust_level"><br/> 客户来源:<input type="text" name="cust_source"><br/> 客户行业:<input type="text" name="cust_industry"><br/> 客户电话:<input type="text" name="cust_mobile"><br/> <input type="submit" value="保存"> </form>
三:测试
在浏览器输入http://localhost/ssh01/save.jsp输入数据,点击提交,
数据库表创建成功,数据成功保存
这样,最简单最原始的ssh框架整合完成。
问题一:在CustomerAction中的获取service
解决方案:
使用监听器,当项目启动的时候监听ServletContext对象的创建,
当ServletContext对象创建的时候加载applicationContext.xml文件,
创建spring工厂初始化bean将spring容器放置在ServletContext域对象中
spring为我们提供了ContextLoaderListener,在web.xml文件中配置该监听器,
它会加载applicationContext.xml,创建spring工厂,
并存放在ServletContext域对象中
代码实现
在web.xml中进行配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
在CustomerService中获取service
ServletContext servletContext = ServletActionContext.getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(servletContext);
CustomerService customerService = (CustomerService) context.getBean("customerService");
问题二:Action由struts2容器管理,service是交给spring容器管理,不能直接注入
如何在action中注入service
解决方案:spring整合struts
前提:导入struts-spring-plugin.jar,在项目启动的时候创建spring工厂,
放置到context域中
方式一:Action还是struts创建,Spring负责为Struts中的Action注入匹配的属性
//由spring容器为action进行属性注入
private CustomerService customerService;
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
原因:为什么直接导入struts2-spring-plugin.jar包就可以直接注入?
在default.properties配置有中struts.objectFactory.spring.autoWire = name
Spring负责为Struts中的Action注入匹配的属性,根据属性的名称注入(默认,可以修改)
方式二:将action交给spring容器来管理,
action是多例的,所以需要配置scope="prototype"属性
修改applicationContext.xml和struts.xml文件
<!-- 将action交给spring容器来管理 -->
<bean id="customerAction" class="com.itheima.web.action.CustomerAction" scope="prototype">
<property name="customerService" ref="customerService"></property>
</bean>
修改struts文件:
<package name="ssh" extends="struts-default" namespace="/">
<action name="customer_*" class="customerAction" method="{1}">
<result name="success">/success.jsp</result>
</action>
</package>
问题三
在dao层,使用Hibernate操作数据库,需要加载Hibernate.cfg.xml配置文件
获取SessionFactory(类似于DataSource数据源)然后获取到session对象
(类似于Connection连接对象,SessionFactory:是重量级并且线程安全的,
所以在项目中只存在一份即
解决方案
将SessionFactory交给spring容器管理:单例
sessionFactory是一个接口,在这里我们使用它的实现类LocalSessionFactoryBean
选择Hibernate5的,因为我们使用的Hibernate是5.0.7版本的
在applicationContext.xml中配置
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 需要指定hibernate的核心配置文件,因为这里面有需要的数据库配置参数 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
</bean>
使用spring提供的HibernateTemplate在dao层操作数据库,将HibernateTemplate交给
spring容器来管理,并注入到dao层
在applicationContext.xml中进行配置
配置Hibernate模板需要注入SessionFactory,因为模板是对Hibernate的包装,底层还是
使用session来操作数据库,所以需要获取到session对象,通过SessionFactory对象
<!-- 配置HibernateTemplate模板 -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<!-- hibernateTemplate模板底层操作数据库是通过session对象,所以需要注入sessionFactory对象获取到session -->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 配置dao -->
<bean id="customerDao" class="com.itheima.dao.impl.CustomerDaoImpl">
<!-- 为 dao注入HibernateTemplate dao层使用HibernateTemplate来操作数据库-->
<property name="hibernateTemplate" ref="hibernateTemplate"></property>
</bean>
修改之后dao层的代码如下
public class CustomerDaoImpl implements CustomerDao {
//注入HibernateTemplate来操作数据库
private HibernateTemplate hibernateTemplate;
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
@Override
public void save(Customer customer) {
hibernateTemplate.save(customer);
}
}
这样直接运行程序,会抛出异常,异常信息为:
Write operations are not allowed in read-only mode (FlushMode.MANUAL):
Turn your Session into FlushMode.COMMIT/AUTO or remove 'readOnly'
marker from transaction definition.
问题:只读模式下(FlushMode.NEVER/MANUAL)写操作不被允许:
把你的Session改成FlushMode.COMMIT/AUTO或者清除事务定义中的readOnly标记
spring会将获取到的session绑定到当前线程中,
为了确保在一个请求中service层和dao层使用的是一个session对象
只有受spring声明式事务的方法才具有写权限。否则在操作的会抛出上面异常
所以还需要在applicationContext.xml中配置spring的声明式事务
<!-- 不管是注解还是xml文件的方式配置spring声明式事务,都需要指定平台事务管理器 -->
<!-- Hibernate 的事务平台管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<!--
需要session对象来开启事务session.beginTransaction()
所以需要注入SessionFactory来获取到session对象
-->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 配置增强/通知spring提供好的,我们来指定规则 -->
<tx:advice transaction-manager="transactionManager" id="my_advice">
<tx:attributes>
<!-- 这些事务隔离级别,事务传播行为,超时信息都是默认值
当进行查询操作的时候,可以修改propagation="SUPPORTS" read-only="true"(查询的时候只读)
-->
<!-- 在开发中的常用配置 -->
<!-- <tx:method name="save*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"/>
<tx:method name="find*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="get*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="update*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"/>
<tx:method name="delete*" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"/> -->
<tx:method name="save" isolation="DEFAULT" propagation="REQUIRED" read-only="false" timeout="-1"/>
</tx:attributes>
</tx:advice>
<!-- aop配置 -->
<aop:config>
<!-- 配置切入点 事务控制在service层 -->
<aop:pointcut expression="execution(* com.itheima.service.impl.*.*(..))" id="myPt"/>
<aop:advisor advice-ref="my_advice" pointcut-ref="myPt"/>
</aop:config>
这样一个纯xml配置整合ssh框架就完成了!!!
在实际的开发中都是注解+xml来完成的。现在我们来对代码进行优化。
在实际的开发中:
我们自定义bean的创建和注入通过注解来完成(CustomerService等)
非自定义的bean交给xml文件配置(例如数据源dataSource和SessionFactory)
优化一:去除struts.xml文件(action的配置使用注解来完成)
使用注解的方式配置action必须导入jar包struts2-convention-plugin-2.3.24.jar
在CustomerAction上面添加注解:
@Namespace("/")
@ParentPackage("struts-default")
public class CustomerAction extends ActionSupport implements ModelDriven<Customer> {
private static final long serialVersionUID = 1L;
//使用ModelDriven模型驱动进行数据封装,必须手动来创建对象
private Customer customer = new Customer();
@Override
public Customer getModel() {
return customer;
}
//由spring容器为action进行属性注入
private CustomerService customerService;
public void setCustomerService(CustomerService customerService) {
this.customerService = customerService;
}
//保存用户的方法
@Action(value="customer_save",results={@Result(name="success",location="/success.jsp")})
public String save(){
customerService.save(customer);
return SUCCESS;
}
}
优化二:所有的自定义bean都使用注解的方式进行配置,
去除applicationContext中的自定义bean
必须在applicationContext中开启组件扫描
在applicationContext中开启组件扫描
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
CustomerAction中的代码
@Namespace("/")
@ParentPackage("struts-default")
@Controller("customerAction")
public class CustomerAction extends ActionSupport implements ModelDriven<Customer>{
@Autowired
private CustomerService customerService;
}
CustomerServiceImpl中配置注解的代码
@Service("customerService")
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerDao customerDao;
CustomerDaoImpl中注解的代码
@Repository("customerDao")
public class CustomerDaoImpl implements CustomerDao {
//注入HibernateTemplate来操作数据库
@Autowired
private HibernateTemplate hibernateTemplate;
优化三:spring的声明式事务使用xml+注解的方式进行配置
减少applicationContext.xml文件中的配置代码
要在applicationContext中开启事务注解支持
<!-- 开启事务注解支持 -->
<tx:annotation-driven/>
事务是在service层进行控制的,在service层上加上事务注解
可以去除applicationContext中配置增强和aop配置的代码
@Service("customerService")
@Transactional
public class CustomerServiceImpl implements CustomerService
优化四:去除持久化类的映射配置文件,使用注解进行代替
其它字段和属性名相同,可以省略@Column
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
//oid
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
去除持久化类的映射配置文件之后,在Hibernate.cfg.xml文件中
引入持久化类映射文件的代码需要修改:
<!-- Hibernate加载映射 -->
<mapping resource="com/itheima/domain/Customer.hbm.xml"/>
Customer.hbm.xml文件已经去除,修改为
<mapping class="com.itheima.domain.Customer"/>
优化五:去除Hibernate.cfg.xml
在前面applicationContext.xml中将SessionFactory交给spring容器管理的时候
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 需要指定hibernate的核心配置文件,因为这里面有需要的数据库配合参数 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
</bean>
指定了核心配置文件,现在需要手动的配置数据库参数以及Hibernate的一些基本配置
如是否显示sql语句,是否格式化sql语句,mysql方言配置等
最终:只留下了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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 开启事务注解支持 -->
<tx:annotation-driven/>
<!-- 开启组件扫描 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 将配置文件加载到容器中 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 进行属性的注入 -->
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 将SessionFactory交给spring容器来管理 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- Hibernate的基本配置 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<!-- 指定扫描包 包下具有@Entity的这些类 -->
<property name="packagesToScan">
<list>
<value>com.itheima.domain</value>
</list>
</property>
</bean>
<!-- 配置HibernateTemplate模板 -->
<bean id="hibernateTemplate" class="org.springframework.orm.hibernate5.HibernateTemplate">
<!-- hibernateTemplate模板底层操作数据库是通过session对象,所以需要注入sessionFactory对象获取到session -->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<!-- 不管是注解还是xml文件的方式配置spring声明式事务,都需要指定平台事务管理器 -->
<!-- Hibernate 的事务平台管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<!--
需要session对象来开启事务session.beginTransaction()
所以需要注入SessionFactory来获取到session对象
-->
<property name="sessionFactory" ref="sessionFactory"></property>
</bean>
</beans>
这样,以xml+注解结合方式整合ssh框架就完成了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。