Spring入门(四)— 整合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