ssh(Spring+Struts2+hibernate)整合

需求:整合ssh框架作一個保存用戶的業務,業務比較簡單,重在ssh框架整合。
建立數據庫和表java

CREATE DATABASE ssh01;
USE DATABASE;
表由Hibernate建立,能夠看配置是否成功

一:導入jar包mysql

  1. Hibernate須要jarweb

    Hibernate基本jar
       mysql驅動  
       c3p0鏈接池
       日誌包
       jpa
  2. Struts須要jar
    Struts2基本jar
  3. Spring須要jarspring

    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
  4. Spring整合websql

    spring-web-4.2.4.RELEASE.jar
  5. Spring整合Hibernate數據庫

    spring-orm-4.2.4.RELEASE.jar
  6. Spring整合Struts2express

    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容器的時候導入apache

    最後:去除重複的jar struts2基本Jar和Hibernate基本Jar中都有
    javassist-3.18.1-GA.jar,刪除一個低版本的,不然在使用的時候會出現問題。瀏覽器

二:須要的配置文件安全

  1. 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>
  2. 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>
  3. applicationContext.xml src下(待會配置)

三:建立service,dao,domain,action

  1. 建立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;
  2. 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);
        }
    }
  3. 建立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();
        }
    }
  4. 建立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;
        }
    }
  5. 在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>
  6. 建立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/>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <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框架就完成了。

相關文章
相關標籤/搜索