1. 程式人生 > >Spring入門(四)— 整合Struts和Hibernate

Spring入門(四)— 整合Struts和Hibernate

patch pan area src 選擇 roo 們的 void prototype

一、Spring整合Struts

1. 初步整合

只要在項目裏面體現spring和 strut即可,不做任何的優化。

  • struts 環境搭建

    1. 創建action

    public class UserAction extends ActionSupport {
        public String save(){
            System.out.println("調用了UserAction的save方法~~!");
        }
    }
    1. 在src下配置struts.xml , 以便struts能根據請求調用具體方法

    <?xml version="1.0" encoding="UTF-8"
    ?> <!-- 1. 導入約束 --> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> <struts> <package name="user" namespace="/" extends="struts-default"> <!-- localhost:8080/項目名/user_save -->
    <action name="user_*" class="com.pri.web.action.UserAction" method="{1}"></action> </package> </struts>
    1. 在web.xml中配置前端控制器,以便strust抓住請求

      <!-- 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>
  • spring 環境搭建

    1. 創建service

    2. 在src下創建applicationContext.xml 並且托管service實現類

    3. 在action裏面獲取工廠進而調用方法

2. 初步整合的問題

該小節講述的是: 上面初步整合遺留下來的問題。

問題:

  1. 每次請求都會創建工廠,解析xml

    ? 解決方案: 工具類 | 靜態代碼塊

    1. 工廠創建時機有點晚, 請求到來的時候才創建工廠

      ?解決方案: 讓工廠提前 ---- 項目發布| 服務器啟動 ----- 使用監聽器(ServletContextListener) ----- 不用我們編寫監聽器 (spring已經寫好了。) ---- 配置監聽器即可。

3. 進階整合

該小節講述的是:把上面初步整合出現的問題給解決了。

該階段整合的目標就是解決上面出現的兩個問題。

 xml裏面配置listener
 <!-- 註冊spring的監聽器 -->
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
      
      <!-- 監聽器裏面會執行工廠的創建,創建工廠需要依賴xml文件,所以我們還得告訴它xml文件在哪裏 -->
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
?
代碼裏面獲取工廠
?
    ApplicationContext context =WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());

4. 進階整合的問題

該小節講述的是: action和service對接的問題。 已經不是工廠創建這類問題

public class UserAction  extends ActionSupport{
?
    public String save(){
        System.out.println("調用了UserAction的save方法~~·");
        
        ApplicationContext context =  WebApplicationContextUtils.getWebApplicationContext(ServletActionContext.getServletContext());
        UserService us = (UserService)context.getBean("userService");
        us.save();
        
        return NONE;
    }
}

分析以上代碼:

? 其實action就是讓service幹活。但是我們想要讓service幹活,必須經過兩個動作:

    1. 先從工具類中獲取工廠
2. 從工廠裏面拿service對象。

一個方法還好,如果action裏面有多個方法呢? 都得這麽寫!!

? 以前的解決方案:

? 工具類 | 靜態代碼塊

? 現在學了spring的解決方案:

? action z只有一個目的就是為了得到service。 service已經在spring容器裏面了。我要讓spring把service主動送過來。 所以這裏必須是註入。 要想完成註入,前提是托管action。

? 總結:

? 把action交給spring托管

? 把service註入到action中來。

5. 最終整合

把action的創建工作交給spring來完成。 註意的是: spring創建action的實例必須是多例的。

  • spring配置

<bean id="userAction" class="com.pri.web.action.UserAction" scope="prototype">
        <property name="userService" ref="userService"></property>
</bean>
?
<bean id="userService" class="com.pri.service.impl.UserServiceImpl"></bean>
  • struts.xml的配置

<!-- 為了能夠讓struts使用到spring創建好的action實例,這裏的class不要寫全路徑了,而是寫spring那邊托管action的id標識符 -->
<action name="user_*" class="userAction" method="{1}">
</action>

6. 最終整合的背後細節

說一說剛才導入的jar包struts-spring-plugin.jar 它裏面的細

  1. struts-spring-plugin.jar 裏面有一個struts-plugin.xml , 其中有兩行關鍵的配置

<bean type="com.opensymphony.xwork2.ObjectFactory" name="spring" class="org.apache.struts2.spring.StrutsSpringObjectFactory" />
    
<!--  Make the Spring object factory the automatic default -->
<constant name="struts.objectFactory" value="spring" />
  1. 在struts的default.properties中也有以下兩個配置

### if specified, the default object factory can be overridden here
### Note: short-hand notation is supported in some cases, such as "spring"
###       Alternatively, you can provide a com.opensymphony.xwork2.ObjectFactory subclass name here
# struts.objectFactory = spring
?
### specifies the autoWiring logic when using the SpringObjectFactory.
### valid values are: name, type, auto, and constructor (name is the default)
struts.objectFactory.spring.autoWire = name
  1. 其實struts.xml裏面的action ,class屬性也可以寫全路徑,也可以寫bean的標識符(id值).

struts 是這麽來的, 拿著這個id值去問spring的工廠要對象,如果找不到, 嘗試自己創建對象, 也就是把class的屬性看成一個類的全路徑地址。

<action name="user_*" class="com.pri.web.action.UserAction" method="{1}"></action>

spring的applicationContext.xml裏面配置如下,但是struts不用它,而且他是多例的,只用有的之後才會創建

<bean id="userAction" class="com.pri.web.action.UserAction" scope="prototype">
    <property name="userService" ref="userService"></property>
</bean>

既然都不用人家的實例,選擇自己創建,為什麽這個action裏面也會有service實例,也會調用setXXX方法呢?

原因就是在strust的defautl.properties裏面有一行關鍵的配置 , 它的特點是struts會按照名字去找spring的工廠要對象註入進來。前提條件是struts能找到spring的工廠,也就是說必須要依賴前面提到的struts-spring-plugin-xx.jar

### specifies the autoWiring logic when using the SpringObjectFactory.
### valid values are: name, type, auto, and constructor (name is the default)
struts.objectFactory.spring.autoWire = name

二、Spring整合Hibernate

1. 初步整合

hibernate環境搭建

  1. 持久化類&映射文件

<hibernate-mapping>
      <class name="com.pri.bean.User" table="t_user">
          <id name="uid">
              <generator class="native"></generator>
          </id>
                
          <property name="username"/>
          <property name="password"/>
       </class>  
 </hibernate-mapping>
  1. hibernate核心配置文件

<hibernate-configuration>
    <session-factory>
            
        <!-- 可以寫三部分內容 -->
        <!-- 1. 核心必須 : 告訴hibernate連接什麽數據庫, 用什麽賬號 , 什麽密碼去連接-->
        <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="hibernate.connection.url">jdbc:mysql:///user</property>
        <property name="hibernate.connection.username">root</property>
        <property name="hibernate.connection.password">root</property>
                
        <!-- 2. 可選配置 -->
        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.hbm2ddl.auto">update</property>
                
        <!-- 3. 映射文件 -->
        <mapping resource="com/pri/bean/User.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

2. 初步整合的問題

  1. 來一次請求就會中一次sessionFactory的創建

    解決方法: 工具類 | 靜態代碼塊

  2. sessionFactory創建時機的問題 .當請求來的之後,才會創建sessionFactory.

解決方法: 提前創建sessionFactory  ----- 項目發布| 服務器啟動  ---  需要用到listener , 但是spring不再做出來新的監聽器了。
?~~~java
其實spring壓根不用再給hibernate做任何的監聽器。 struts已經有監聽器,它的監聽器用於捕獲項目發布, 只要捕獲到了一定會解析spring的applicationContext.xml文件。所以spring建議hibernate的核心配置文件,就放到applicationContext.xml中來。
?~~~

3. 進階整合(保留hibernate核心配置文件)

該階段整合的目標是:在項目啟動的時候,創建hibernate的sessionFactory工廠。 需要在applicationContext.xml中配置LocalSessionFactoryBean.

  • applicationContext.xml中配置如下

<!-- 配置該類,spring會創建它的實例,並且裏面會包含了解析hibernate核心文件代碼以及創建SessionFactory -->
<bean id="sessionFactory"class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <!-- 要求指定hibernate核心文件所在位置 -->
     <property name="configLocations">
       <array>
            <value>classpath:hibernate.cfg.xml</value>
        </array>
     </property>
</bean>
...
<bean id="userDao" class="com.pri.dao.impl.UserDaoImpl">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean>
  • dao層代碼如下:

public class UserDaoImpl implements UserDao {
     //聲明sessionFactory
     private SessionFactory sessionFactory;
     //讓spring註入sessionFactory進來
     public void setSessionFactory(SessionFactory sessionFactory) {
           this.sessionFactory = sessionFactory;
     }
            
     @Override
     public void save() {
           System.out.println("調用了UserDaoImpl的save方法~~~");
           Session session = sessionFactory.openSession();
               
           Transaction transaction = session.beginTransaction();
                
           User user = new User();
           user.setUsername("bb");
           user.setPassword("123");
           session.save(user);
                
           transaction.commit();
           session.close();
           //sessionFactory.close();
      }
}

4. 進階整合(去掉hibernate核心配置文件)

該階段整合的目標是: 刪除掉hibernate.cfg.xml,但是它裏面的內容還是要有。 把這三部分內容放到spring裏面來寫。

<!-- 配置該類,spring會創建它的實例,並且裏面會包含了解析hibernate核心文件代碼以及創建SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- 要求指定hibernate核心文件所在位置 -->
        <!-- <property name="configLocations">
                <array>
                    <value>classpath:hibernate.cfg.xml</value>
                </array>
            </property> -->
            
      <!-- 1. 核心必須配置  連接什麽數據庫,怎麽連數據庫-->
      <property name="dataSource" ref="dataSource"></property>
            
      <!-- 2. 可選配置 -->
      <property name="hibernateProperties">
            <props>
               <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
               <prop key="hibernate.show_sql">true</prop>
               <prop key="hibernate.format_sql">true</prop>
               <prop key="hibernate.hbm2ddl.auto">update</prop>
             </props>
      </property>
            
      <!-- 3. 映射文件導入 -->
      <property name="mappingResources">
          <array>
               <value>com/pri/bean/User.hbm.xml</value>
           </array>
       </property>
</bean>

5. 進階整合(細節)

  • 使用連接池

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${driverClass}"></property
    <property name="jdbcUrl" value="${jdbcUrl}"></property
    <property name="user" value="${user}"></property
    <property name="password" value="${password}"></roperty>
</bean>
  • 使用jdbc.properties

  1. jdbc.properties:

driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///users
user=root
password=root
  1. xml:

<context:property-placeholder location="classpath:jdbc.properties"/>
?
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${driverClass}"></property>
    <property name="jdbcUrl" value="${jdbcUrl}"></property>
    <property name="user" value="${user}"></property>
    <property name="password" value="${password}"></property>
</bean>
  • 多個映射文件的處理

以前的寫法:
    <property name="mappingResources">
        <array>
            <value>com/pri/bean/User.hbm.xml</value>
        </array>
    </property>
?
現在的寫法:
    <property name="mappingDirectoryLocations" value="classpath:com/pri/bean"/>

6. 最終整合

講述的是: 使用Hibernate模板 , 簡化我們dao層的CRUD操作

  1. 開啟事務

一定要開啟事務,否則會拋出異常

技術分享圖片

技術分享圖片

在applicationContext.xml中開啟事務

<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
  1. service層代碼

@Transactional
public class UserServiceImpl implements UserService {}
  1. dao層代碼

public class UserDaoImpl extends HibernateDaoSupport implements UserDao {
     @Override
     public void save() {
         User user = new User();
         user.setUsername("admin");
         user.setPassword("123456");
        
         getHibernateTemplate().save(user);
     }
}
  1. dao層一定要註入sessionFactory

<bean id="userDao" class="com.pri.dao.impl.UserDaoImpl">
    <property name="sessionFactory" ref="sessionFactory"></property>
</bean>

三、 Hibernate模板

1. hibernate模板的API

  • save方法

@Override
public void save(User user) {
    getHibernateTemplate().save(user);
}
  • upate方法

public void update(User user) {
    getHibernateTemplate().update(user);
}
  • delete方法

public void delete(User user) {
    getHibernateTemplate().delete(user);
}
  • get方法

public User get(Integer id) {
    return getHibernateTemplate().get(User.class, id);
}
  • load方法

public User load(Integer id) {
    return getHibernateTemplate().load(User.class, id);
}
  • 查詢總條目數

@Override
public int findCount() {
        String hql = "select count(*) from User";
    //查詢的時候,返回總是list, 區別就是list裏面放的是什麽數據,因為查詢總數,回來肯定就是一個數字 
        //list.add(500)
        List<Long> list = (List<Long>) getHibernateTemplate().find(hql);
        if(list.size()>0){
            return list.get(0).intValue();
        }
        return 0;
}
  • 使用 HQL 方式查詢

    @Override
    public List<User> findByHQL() {
        
        //hql其實就是sql語句這種寫法的hibernate版本。  表名 --- 類名
        String hql = "from User";
        return (List<User>) getHibernateTemplate().find(hql);
    }
  • 使用QBC方式查詢

 @Override
 public List<User> findByQBC() {
        //QBC的方式是面向對象編程。 也就是我們再也不能寫sql語句, hql語句也寫不了。  對象。方法
        
        //這行離線對象對應的sql語句應該是  select * from user;
        //如果想知道離線對象更詳細的用法,以及它的作用。 請看 8號上午上課的11點左右。
        DetachedCriteria criteria = DetachedCriteria.forClass(User.class);
//      criteria.add(Restrictions.like(propertyName, value))
        return (List<User>) getHibernateTemplate().findByCriteria(criteria);
 }

四、HibernateTemplate 懶加載的問題

  • 拋出異常

    could not initialize proxy - no Session

技術分享圖片

  • 原因

  spring管理這個session,也是放置到Theradlocal裏面去的。 只要在業務邏輯層提交了事務,那麽session就關閉掉。所以使用懶加載就會有一個問題,無法查詢,因為沒有了session。 為了解決這個問題,我們需要在web.xml中配置一個過濾器,以便讓session的關閉稍微推後、延遲點。  技術分享圖片

技術分享圖片

  事實上spring自己整合了dao層的事務管理,也提供了dao層的模板支持,它為了確保這兩者使用的連接對象是同一個,就在底層使用ThreadLocal來存儲session連接 ,詳情請參看下面的時序圖. 雖然該圖描述的是jdbc模板的技術,但是hibernate模板的技術也同樣適用。

技術分享圖片

技術分享圖片

  • 解決辦法:

    在web.xml中配置過濾器。這個過濾器會對來訪的請求進行過濾,以便知道哪些請求延遲關閉session。

註意: 要在struts的過濾器之前添加 否則無效 ,原因是struts的過濾器抓住請求後,後面不會給配置的這個過濾器了。

<filter>
    <filter-name>OpenSession</filter-name>
    <filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>
          
<filter-mapping>
    <filter-name>OpenSession</filter-name>
    <url-pattern>*.action</url-pattern> <!-- 匹配後面帶有.action的請求 -->
</filter-mapping>
  1. 有的同學可能會想,為什麽這裏過濾的是.action呢 ? 直接寫/可以不?

    其實也可以,但是/*能夠包含的範圍更廣,也就是不管是什麽請求,都會讓這個session延遲關閉,這就造成資源的浪費了。

  2. 有的同學可能還會想,我們的action訪問,不是會自動帶有.action的後綴麽?

    其實這是一種錯誤的想法,我們在頁面上的寫的時候路徑是這樣的

    ? localhost:8080/項目/user_save

    人家的那個action的後綴規定,只是說,你在訪問action的時候可以帶上這個.action,也可以不帶,而不是說它會自己帶這個.action過來。

Spring入門(四)— 整合Struts和Hibernate