我使用Spring 3.0.0.RC1制作了一个非常简单的REST控制器方法,它使用hibernate来执行查询。查询大约需要十秒钟才能完成。我是故意这样做的,这样我就可以向我的控制器发出两个请求。
然后我启动这两个请求,并在MySQL(我的数据库后端)中查询“显示完整的进程列表”,令我惊讶的是,只有一个请求正在进行中。一个请求将成功,一个请求将失败,异常为“org.hibernate.SessionException:Session is closed!”如果我做两个以上的请求,只有一个会成功,其他请求会以同样的方式失败。并且一次只会有一个查询,即使应该有多个。
这怎么可能?有什么建议吗?
告诉你一些我的配置,这里是我用于控制器的配置:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/MyDb" />
<property name="username" value="angua" />
<property name="password" value="vonU" />
<property name="initialSize" value="2" />
<property name="maxActive" value="5" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="annotatedClasses">
<list>
<value>tld.mydomain.sample.entities.User</value>
<value>tld.mydomain.sample.entities.Role</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">false</prop>
</props>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean name="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor">
<property name="sessionFactory" ref="sessionFactory"/>
<property name="flushMode" value="0" />
</bean>
<bean id="txProxyTemplate" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<props>
<prop key="create*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_SUPPORTS,readOnly</prop>
</props>
</property>
</bean>
<bean id="userService" parent="txProxyTemplate">
<property name="target">
<bean class="tld.mydomain.business.UserServiceImpl"/>
</property>
<property name="proxyInterfaces" value="tld.mydomain.business.UserService"/>
</bean>
<context:component-scan base-package="tld.mydomain"/>
<bean class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<ref bean="openSessionInViewInterceptor" />
</list>
</property>
</bean>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:prefix="" p:suffix=".jsp"/>
<bean name="jsonView" class="org.springframework.web.servlet.view.json.JsonView">
<property name="encoding" value="ISO-8859-1"/>
<property name="contentType" value="application/json"/>
</bean>
最后是我的控制器代码:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.json.JsonView;
import tld.mydomain.sample.business.UserService;
@Controller
@RequestMapping("/exp/*")
public class ExperimentsController {
@Autowired
private UserService userService;
@Autowired
private JsonView jsonView;
@RequestMapping(value="/long", method = RequestMethod.GET)
public ModelAndView lang() {
ModelAndView mav = new ModelAndView(jsonView);
userService.longQuery("UserA");
userService.longQuery("UserB");
return mav;
}
}
更新:这是UserServiceImpl
public class UserServiceImpl extends AbstractCRUDServiceImpl<User, String> {
@SuppressWarnings("unchecked")
@Override
public List<User> longQuery(String username) {
String like = "0" + username + "-%";
return DAO.getSession().createCriteria(User.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY).addOrder(Order.asc("name"))
.createCriteria("interests").add(Restrictions.like("userPrefixedId", like))
.createCriteria("community").add(Restrictions.like("userPrefixedAuthorId", like))
.createCriteria("member").add(Restrictions.like("userPrefixedGroupId", like))
.add(Restrictions.isNotEmpty("skills"))
.list();
}
}
(有意使查询变慢,以便我可以轻松地重现错误,以便同时运行多个请求并查看数据库中同时运行的查询数量)
你还需要我的AbstractCRUDServiceImpl和GenericCRUDDAO:
public abstract class AbstractCRUDServiceImpl<Entity extends PublishableEntity, PkID extends Serializable> implements CRUDService<Entity, PkID> {
protected GenericCRUDDAO<Entity, PkID> DAO = new GenericCRUDDAO<Entity, PkID>(dataType());
@Override
public void create(Entity entity) {
DAO.create(entity);
}
@Override
public void delete(Entity entity) {
DAO.create(entity);
}
@Override
public Entity read(PkID entityPk) {
return DAO.read(entityPk);
}
@Override
public void update(Entity entity) {
DAO.update(entity);
}
private Class<PkID> pkType = null;
@SuppressWarnings("unchecked")
public Class<PkID> pkType() {
if(pkType != null)
return pkType;
// Backup solution in case datatype hasn't been set
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
pkType = (Class<PkID>) paramType.getActualTypeArguments()[1];
} else if (type instanceof Class) {
pkType = (Class<PkID>) type;
}
return pkType;
}
private Class<Entity> dataType = null;
@SuppressWarnings("unchecked")
private Class<Entity> dataType() {
if(dataType != null)
return dataType;
// Backup solution in case datatype hasn't been set
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
dataType = (Class<Entity>) paramType.getActualTypeArguments()[0];
} else if (type instanceof Class) {
dataType = (Class<Entity>) type;
}
return dataType;
}
}
在GenericCRUDDAO中,PublishableEntity是我所有实体的后代。它有一些简单的方便方法,例如检查实体是否有效以及应该发布它的哪些部分与在toString或类似中使用时保持自身
public class GenericCRUDDAO<EntityType extends PublishableEntity, PkID extends Serializable> implements CRUDDAO<EntityType, PkID> {
public GenericCRUDDAO() {}
public GenericCRUDDAO(Class<EntityType> datatype) {
this.setDataType(datatype);
}
private static SessionFactory sessionFactory = null;
public void setSessionFactory(SessionFactory sf) {
System.err.println("Setting SessionFactory for class " + this.getClass().getName());
sessionFactory = sf;
}
private Session session = null;
public Session getSession() {
if(session != null) {
if(session.isOpen())
return session;
}
if(sessionFactory == null)
Util.logError("sessionFactory is null");
session = ((SessionFactory) sessionFactory).getCurrentSession();
return session;
}
public void create(EntityType entity)
{
getSession().save(entity);
}
@SuppressWarnings("unchecked")
public EntityType read(PkID id)
{
return (EntityType) getSession().get(dataType(), id);
}
public void update(EntityType entity)
{
getSession().update(entity);
}
public void delete(EntityType entity) {
getSession().delete(entity);
}
public void delete(PkID id)
{
EntityType entity = read(id);
getSession().delete(entity);
}
private Class<EntityType> dataType = null;
@SuppressWarnings("unchecked")
private Class<EntityType> dataType() {
if(dataType != null)
return dataType;
// Backup solution in case datatype hasn't been set
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
ParameterizedType paramType = (ParameterizedType) type;
dataType = (Class<EntityType>) paramType.getActualTypeArguments()[0];
} else if (type instanceof Class) {
dataType = (Class<EntityType>) type;
}
return dataType;
}
public void setDataType(Class<EntityType> datatype) {
this.dataType = datatype;
}
}
我希望配置和代码能让我明白为什么我似乎一次只能进行一次查询而不会让他们陷入其中。
干杯
的Nik
答案 0 :(得分:1)
看起来很标准。
这当然假定:
JsonView
是线程安全的 - 我认为它是UserService
的实施也是线程安全的通常使用这种服务风格的单身人士,除非你做了像UserServiceImpl
从我在GenericCRUDDAO
中看到的情况,我会密切关注会员session
。如果GenericCRUDDAO是单例(从每个域对象看一个),那么你将在那里遇到一点麻烦。
getSession()
的实施实际上可以缩短为:
public Session getSession() {
return ((SessionFactory) sessionFactory).getCurrentSession();
}
这应该是线程安全的,假设sessionFactory使用线程本地会话。
答案 1 :(得分:1)
在写完我的更新后,我一直在反复查看相同的代码,直到它让我看到我一直在看的东西:
private Session session = null;
public Session getSession() {
if(session != null) {
if(session.isOpen())
return session;
}
if(sessionFactory == null)
Util.logError("sessionFactory is null");
session = ((SessionFactory) sessionFactory).getCurrentSession();
return session;
}
由于服务是单例,并且它继承自AbstractCRUDSerivceImpl,该新闻DAO,“私有会话会话”实际上成为静态实例。并且“if(session.isOpen())返回会话;”成为竞争条件。我现在把功能简化为:
public Session getSession() {
return ((SessionFactory) sessionFactory).getCurrentSession();
}
这似乎解决了我的问题。这看起来像是一个解决方案,还是我还有其他明显的问题?
干杯
的Nik