结合上一篇的ssh,讨论延迟加载问题
什么是延迟加载问题 ?
业务层查询数据,返回后,session关闭了, 表现层获取数据如果关联延迟数据,无法初始化 ! (No Session 延迟加载问题 )
举例:
<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">
<!-- 引入外部属性配置文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driverClass}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!-- 方式二: (推荐)将hibernate参数配置到spring文件 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<!-- 1.数据源 -->
<property name="dataSource" ref="dataSource"/>
<!-- 2.Hibernate的属性 -->
<property name="hibernateProperties">
<props>
<!-- 设置方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<!-- 设置打印sql语句 -->
<prop key="hibernate.show_sql">true</prop>
<!-- 格式化sql语句 -->
<prop key="hibernate.format_sql">true</prop>
<!-- 自动建表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
<!-- 3.mapping映射 -->
<property name="mappingResources">
<list>
<value>cn/itcast/ssh/domain/Book.hbm.xml</value>
<value>cn/itcast/ssh/domain/User.hbm.xml</value>
</list>
</property>
</bean>
<!-- 配置声明式事务处理 -->
<!-- 平台事务管理器的实现 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<!-- 注入sessin工厂 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 具体事务的属性策略 -->
<tx:attributes>
<tx:method name="save*"/>
<tx:method name="update*"/>
<tx:method name="delete*"/>
<tx:method name="find*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 事务的切入点和切面配置 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="bean(*Service)" id="txPointcut"/>
<!-- 切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<bean id="userDao" class="cn.itcast.ssh.dao.UserDaoImpl">
<!-- 注入sessionFactory,这是在Dao层使用hibernateTemplate的条件,用来操作数据库的crud -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="userService" class="cn.itcast.ssh.service.UserServiceImpl">
<!-- 注入dao -->
<property name="userDao" ref="userDao"/>
</bean>
<bean id="bookAction" class="cn.itcast.ssh.action.BookAction" scope="prototype">
<property name="userService" ref="userService"/>
</bean>
</beans>
book类
public class Book {
private Integer id;
private String name;
private Double price;
private User user;
......
}
Book.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.ssh.domain.Book" table="book">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="price"/>
<many-to-one name="user" class="cn.itcast.ssh.domain.User" column="uid"></many-to-one>
</class>
</hibernate-mapping>
user类:
public class User {
private Integer id;
private String name;
private Integer age;
private List<Book> books;
......
}
User.hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.ssh.domain.User" table="user">
<id name="id">
<generator class="native"/>
</id>
<property name="name"/>
<property name="age"/>
<set name="books" cascade="save-update,delete" inverse="true">
<key column="uid"></key>
<one-to-many class="cn.itcast.ssh.domain.User"></one-to-many>
</set>
</class>
</hibernate-mapping>
我们在action中获取User
// action
public class BookAction extends ActionSupport implements ModelDriven<Book> {
public IUserService userService;
public void setUserService(IUserService userService){
this.userService = userService;
}
//业务方法:保存图书
public String add(){
List<User> all = userService.findAll();
System.out.println(all);
return "NONE";
}
}
// service
public class UserServiceImpl implements IUserService {
public List<User> findAll(){
return userDao.findAll();
}
}
// dao
public class UserDaoImpl extends HibernateDaoSupport implements IUserDao {
public List<User> findAll(){
return super.getHibernateTemplate().loadAll(User.class);
}
}
查询结果如下:
因为action中执行代码出异常,是不会在server控制台打印保存信息的,所以我们得手动捕捉异常
public String add() {
try {
List<User> all = userService.findAll();
System.out.println(all);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("打印结束");
return "NONE";
}
我们访问action的add方法
,执行结果如下:
我们在dao中执行loadAll(User.class)
方法时,执行sql,但这个sql只查询User表,而user对象中的books属性,只有当使用的时候,才会通过session去数据库中查询.
当spring整合了hibernate后,我们使用的HibernateTemplate,本质上使用的是getCurrentSession(绑定在当前线程上的session)
getCurrentSession方法创建的Session实例会被绑定到当前线程中,它在提交或回滚操作时会自动关闭。
而当前被事务管理的是service,也就等于在service方法执行的第一步,我通过session开启了事务,当service执行结束时,我们对事务进行了提交,此时session关闭
而我们在action中执行System.out.println(all)
;会调用user对象的books属性,但是此时session已经关闭了,无法在去查询,所以报了no session异常
问题: 如何解决延迟加载问题 ?
方案一: 在Xxx.hbm.xml中配置为立即加载 lazy=false (不推荐 )
方案二: Service方法返回前, 对延迟数据进行初始化 (缺点多写代码 )
//查询:复杂条件查询,根据书名模糊查询
public class UserServiceImpl implements IUserService {
public List<User> findAll(){
List<User> all = userDao.findAll();
// 通过sout方法,调用user中的books
System.out.println(all);
// 或者使用 Hibernate.initialize();
return all;
}
}
方案三: spring提供了OpenSessionInView 机制 (将Session开启到表现层 最前面 Filter )
Spring 提供 OpenSessionInViewFilter `[注意:需要配置在struts2 Filter前面,否则不起作用 ]
web.xml
<!-- OpenSessionInView机制:会将会话到表现层,让会话在请求结束之后关闭,延迟了session关闭,需要放置到struts2的过滤器的前面 -->
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate5.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
OpenSessionInViewFilter:在request过程中维持session(此session是hibernate的session)。延迟session的关闭,直到request结束,再自动关闭session,也就是说,直到表现层的数据全部加载完毕,再关闭Session。