2009-10-27 58 views
1

我用Spring 3.0.0.RC1做了一個非常簡單的REST控制器方法,它使用hibernate執行查詢。查詢需要大約十秒鐘才能完成。我已經做了這個意圖,以便我可以向控制器發出兩個請求。從兩個線程使用相同的服務和DAO

然後,我啓動了這兩個請求,並在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; 
    } 
} 

的配置和代碼使其在什麼部位應該公佈VS保持自身顯而易見的是,爲什麼我似乎一次只能做一個查詢,而沒有將它們放在一個腳上。

乾杯

回答

1

看起來非常標準的給我。

當然,這是假設:

  • JsonView是線程安全的 - 我認爲這是
  • 你實現UserService也線程安全

通常這種風格的服務單的他們是除非你已經做了類似保持狀態的成員UserServiceImpl

從我在GenericCRUDDAO中可以看到的情況,我會密切關注會員session。如果GenericCRUDDAO是單身人士(根據它的外觀每個域對象一個),那麼你將在那裏遇到一些麻煩。

getSession()實施實際上可以縮短到:

public Session getSession() { 
    return ((SessionFactory) sessionFactory).getCurrentSession(); 
} 

這應該是線程安全的,假設SessionFactory則是使用線程本地會話。

+0

沒問題,我已經更新了帖子,包括UserSerivceImpl,並AbstractCRUDSerivceImpl它延伸並GenericCRUDDAO它使用。非常感謝你回到我這麼快 – niklassaers 2009-10-27 09:47:22

+0

呵呵,似乎我們發現了同樣的錯誤。非常感謝您的幫助。我不太確定發生了什麼事,但是當我點擊接受時,答案中的「投票」就下降到零,而當我再次按下「上」時,它會顯示「投票太舊,無法更改,除非編輯了此答案」 。任何機會,你會做一個小小的編輯,所以我可以再次投票呢? ;-) – niklassaers 2009-10-27 16:50:50

+0

不用擔心,一個小編輯done..ta – 2009-10-27 18:06:26

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,「private Session session」實際上變成了一個靜態實例。並且「if(session.isOpen())返回會話;」成爲競爭條件。我現在減少了功能:

public Session getSession() { 
    return ((SessionFactory) sessionFactory).getCurrentSession(); 
} 

這似乎解決了我的問題。這看起來像是一種解決方案嗎?還是我還有其他明顯的問題?

乾杯

+0

沒有看到你的答案那裏...我已經更新了我的。 – 2009-10-27 13:28:40