本文主要介紹Sprint整合Struts和Hibernate的基本使用。 ...
一、Spring整合Struts
1. 初步整合
只要在項目裡面體現spring和 strut即可,不做任何的優化。
-
struts 環境搭建
-
創建action
public class UserAction extends ActionSupport { public String save(){ System.out.println("調用了UserAction的save方法~~!"); } }
-
在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>
-
在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 環境搭建
-
創建service
-
在src下創建applicationContext.xml 並且托管service實現類
-
在action裡面獲取工廠進而調用方法
-
2. 初步整合的問題
該小節講述的是: 上面初步整合遺留下來的問題。
問題:
-
每次請求都會創建工廠,解析xml
解決方案: 工具類 | 靜態代碼塊
-
工廠創建時機有點晚, 請求到來的時候才創建工廠
解決方案: 讓工廠提前 ---- 項目發佈| 伺服器啟動 ----- 使用監聽器(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
它裡面的細
-
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" />
-
在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
-
其實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環境搭建
-
持久化類&映射文件
<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>
-
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. 初步整合的問題
-
來一次請求就會中一次sessionFactory的創建
解決方法: 工具類 | 靜態代碼塊
-
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
-
jdbc.properties:
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql:///users
user=root
password=root
-
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操作
-
開啟事務
一定要開啟事務,否則會拋出異常
在applicationContext.xml中開啟事務
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>
-
service層代碼
@Transactional public class UserServiceImpl implements UserService {}
-
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); } }
-
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>
-
有的同學可能會想,為什麼這裡過濾的是.action呢 ? 直接寫/可以不?
其實也可以,但是/*能夠包含的範圍更廣,也就是不管是什麼請求,都會讓這個session延遲關閉,這就造成資源的浪費了。
-
有的同學可能還會想,我們的action訪問,不是會自動帶有.action的尾碼麽?
其實這是一種錯誤的想法,我們在頁面上的寫的時候路徑是這樣的
localhost:8080/項目/user_save