Spring MVC 3 + Hibernate:对象的不同地址(一次通过服务加载,一次通过实体加载)

发布于 2024-11-16 17:17:14 字数 8895 浏览 1 评论 0原文

我有一个实体“帐户”,它与实体“角色”具有多对多关系。当我通过 JSP 表单添加新的“帐户”时,一切正常。我可以通过复选框选择我想要与帐户关联的所有角色。

如果我现在尝试编辑添加的帐户,我会再次看到角色的复选框,但没有一个被选中(应选中我添加帐户时选中的复选框)。

我调试了整个过程,我意识到,通过 account.getRoles() 加载的 Role 对象的地址与通过 roleService.list() 加载的地址不同。所以我想会话肯定有问题,但我不知道应该从哪里开始搜索:S...

帐户实体提取:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, fetch = FetchType.EAGER)
@JoinTable(name = "Account_Role", joinColumns = { 
    @JoinColumn(name = "Account_id") }, 
    inverseJoinColumns = { @JoinColumn(name = "Role_id") })
private Set<Role> roles = new HashSet<Role>(0);

角色服务提取:

@Transactional
public List<Role> list() {
    return roleDAO.list();
}

角色 DAO 摘录:

@Autowired
private SessionFactory sessionFactory;

@SuppressWarnings("unchecked")
public List<Role> list() {
    return sessionFactory.getCurrentSession().createQuery("from Role").list();
}

帐户的 edit.jsp 摘录

    <tr>
        <td style="width:75px">
            <label for="roles"><spring:message code="labels.account.form.roles" text="Roles" /></label>
        </td>
        <td>
            <form:checkboxes path="roles" items="${roleList}" itemLabel="name" itemValue="idAsString" delimiter="<br/>"/> 
        </td>

</tr>

帐户控制器摘录

/**
 * edit() - Save edited item
 * @param id of changed object
 * @param item which has been changed
 * @return  path to view
 */
@RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String edit(@PathVariable("id") Long id, @ModelAttribute("item") Account item, BindingResult bindingResult, Model model) {
    accountService.merge(item);

    return "redirect:/account/list";
}

/**
 * edit() - Edit an item
 * @param id of item to change
 * @param model to store item
 * @return  path to view
 */
@RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public String editForm(@PathVariable("id") Long id, Model model) {
    Account item = accountService.load(id);

    for(Role r : item.getRoles()){
        System.out.println("ROLE ITEM: ID="+r.getId()+" | NAME="+r.getName()+" | HASH="+r);
    }

    for(Role r : roleService.list()){
        System.out.println("ROLE SERVICE: ID="+r.getId()+" | NAME="+r.getName()+" | HASH="+r);
    }

    model.addAttribute("item", item);
    model.addAttribute("roleList", roleService.list());

    return "account/edit";
}

spring-servlet.xml 摘录:

<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />

<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans -->
<context:component-scan base-package="fi.java.elearning.*" />

<!-- Configures the annotation-driven Spring MVC Controller programming model -->
<mvc:annotation-driven /> 

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />

<!-- Include Tiles for View Rendering -->
<bean id="viewResolver"
    class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass">
        <value>
            org.springframework.web.servlet.view.tiles2.TilesView
        </value>
    </property>
</bean>

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/configurations/tiles/tiles.xml</value>
        </list>
    </property>
</bean>

<!-- multipart file resolver bean -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>   


<!-- multi language support -->

<bean id="localeResolver"
    class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="de" />
</bean>

<bean id="localeChangeInterceptor"
    class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="language" />
</bean>

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
    <property name="interceptors">
       <list>
        <ref bean="localeChangeInterceptor" />
       </list>
    </property>
</bean>

<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="languages/messages" />
</bean>

<!-- Import Hibernate Context -->
<import resource="configurations/hibernate/hibernate-context.xml" />

<!-- Import Spring Security -->
<import resource="configurations/spring/spring-security.xml" />

从 web.xml 中提取:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring-servlet.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
<error-page>
  <error-code>404</error-code>
  <location>/WEB-INF/views/contents/exceptions/404.jsp</location>
</error-page>

从 hibernate.context.xml 中摘录:

<context:property-placeholder location="/WEB-INF/configurations/hibernate/database.properties" />

<!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions -->
<!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html -->                           
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html -->
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
             p:dataSource-ref="dataSource"
             p:configLocation="${hibernate.config}"
             p:packagesToScan="fi.java.elearning"/>

<!-- Declare a datasource that has pooling capabilities-->   
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
            destroy-method="close"
            p:driverClass="${app.jdbc.driverClassName}"
            p:jdbcUrl="${app.jdbc.url}"
            p:user="${app.jdbc.username}"
            p:password="${app.jdbc.password}"
            p:acquireIncrement="5"
            p:idleConnectionTestPeriod="60"
            p:maxPoolSize="100"
            p:maxStatements="50"
            p:minPoolSize="10" />

<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" 
            p:sessionFactory-ref="sessionFactory" />

<!-- Enable annotation style of managing transactions -->
<tx:annotation-driven transaction-manager="transactionManager" />   

我在控制器中使用的 System.out.println() 显示了(据我认为)问题:

从控制台提取:

角色项目:ID=1 |名称=ACCOUNT_MANAGER |؆HASH=fi.java.elearning.data.model.Role@9293709 角色服务:ID=1 | NAME=ACCOUNT_MANAGER |؆HASH=fi.java.elearning.data.model.Role@2721e92 角色服务:ID=2 | NAME=Test |؆HASH=fi.java.elearning.data.model.Role@1235047f

所以 ITEM 是我添加帐户时最初检查的角色。当我编辑帐户时,应该已经检查了这一点。但事实并非如此。我认为这与不同的地址/参考(9293709/2721e92)有关。如果我在控制器操作顶部添加 @Transcational 注释,则引用是正确的。然后复选框被选中。但这对我来说没有意义。事务注释应该在 ServiceLayer 中,不是吗?

非常感谢您的帮助...

I have an Entity "Account" which has a ManyToMany Relationship to an Entity "Role". When I add a new "Account" via JSP Form everything works fine. I can choose there all the Roles I d like to associate with the Account via Checkboxes.

If I now try to edit the added Account, I get the checkboxes for the roles again, but none of them is checked (the ones that I checked when I added the Account should be checked).

I debugged the whole thing and what I realized is, that the address of the Role object which is loaded through account.getRoles() are not the same as the ones loaded via roleService.list(). So i guess there has to be a problem concerning the session, but I have no idea where I should start searching :S...

Extract of Account Entity:

@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, fetch = FetchType.EAGER)
@JoinTable(name = "Account_Role", joinColumns = { 
    @JoinColumn(name = "Account_id") }, 
    inverseJoinColumns = { @JoinColumn(name = "Role_id") })
private Set<Role> roles = new HashSet<Role>(0);

Extract of Role Service:

@Transactional
public List<Role> list() {
    return roleDAO.list();
}

Extract of Role DAO:

@Autowired
private SessionFactory sessionFactory;

@SuppressWarnings("unchecked")
public List<Role> list() {
    return sessionFactory.getCurrentSession().createQuery("from Role").list();
}

Extract of edit.jsp of Account

    <tr>
        <td style="width:75px">
            <label for="roles"><spring:message code="labels.account.form.roles" text="Roles" /></label>
        </td>
        <td>
            <form:checkboxes path="roles" items="${roleList}" itemLabel="name" itemValue="idAsString" delimiter="<br/>"/> 
        </td>

</tr>

Extract of Account Controller

/**
 * edit() - Save edited item
 * @param id of changed object
 * @param item which has been changed
 * @return  path to view
 */
@RequestMapping(value="/edit/{id}", method=RequestMethod.POST)
public String edit(@PathVariable("id") Long id, @ModelAttribute("item") Account item, BindingResult bindingResult, Model model) {
    accountService.merge(item);

    return "redirect:/account/list";
}

/**
 * edit() - Edit an item
 * @param id of item to change
 * @param model to store item
 * @return  path to view
 */
@RequestMapping(value="/edit/{id}", method=RequestMethod.GET)
public String editForm(@PathVariable("id") Long id, Model model) {
    Account item = accountService.load(id);

    for(Role r : item.getRoles()){
        System.out.println("ROLE ITEM: ID="+r.getId()+" | NAME="+r.getName()+" | HASH="+r);
    }

    for(Role r : roleService.list()){
        System.out.println("ROLE SERVICE: ID="+r.getId()+" | NAME="+r.getName()+" | HASH="+r);
    }

    model.addAttribute("item", item);
    model.addAttribute("roleList", roleService.list());

    return "account/edit";
}

Extract from spring-servlet.xml:

<!-- Activates various annotations to be detected in bean classes -->
<context:annotation-config />

<!-- Scans the classpath for annotated components that will be auto-registered as Spring beans -->
<context:component-scan base-package="fi.java.elearning.*" />

<!-- Configures the annotation-driven Spring MVC Controller programming model -->
<mvc:annotation-driven /> 

<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
<mvc:resources mapping="/resources/**" location="/resources/" />

<!-- Include Tiles for View Rendering -->
<bean id="viewResolver"
    class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass">
        <value>
            org.springframework.web.servlet.view.tiles2.TilesView
        </value>
    </property>
</bean>

<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
    <property name="definitions">
        <list>
            <value>/WEB-INF/configurations/tiles/tiles.xml</value>
        </list>
    </property>
</bean>

<!-- multipart file resolver bean -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>   


<!-- multi language support -->

<bean id="localeResolver"
    class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="de" />
</bean>

<bean id="localeChangeInterceptor"
    class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="language" />
</bean>

<bean class="org.springframework.web.servlet.mvc.support.ControllerClassNameHandlerMapping" >
    <property name="interceptors">
       <list>
        <ref bean="localeChangeInterceptor" />
       </list>
    </property>
</bean>

<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basename" value="languages/messages" />
</bean>

<!-- Import Hibernate Context -->
<import resource="configurations/hibernate/hibernate-context.xml" />

<!-- Import Spring Security -->
<import resource="configurations/spring/spring-security.xml" />

Extract from web.xml:

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/spring-servlet.xml</param-value>
</context-param>
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
  <filter-name>springSecurityFilterChain</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSecurityFilterChain</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
  <servlet-name>spring</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
  <servlet-name>spring</servlet-name>
  <url-pattern>/</url-pattern>
</servlet-mapping>
<error-page>
  <error-code>404</error-code>
  <location>/WEB-INF/views/contents/exceptions/404.jsp</location>
</error-page>

Extract from hibernate.context.xml:

<context:property-placeholder location="/WEB-INF/configurations/hibernate/database.properties" />

<!-- Declare the Hibernate SessionFactory for retrieving Hibernate sessions -->
<!-- See http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/orm/hibernate3/annotation/AnnotationSessionFactoryBean.html -->                           
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/SessionFactory.html -->
<!-- See http://docs.jboss.org/hibernate/stable/core/api/index.html?org/hibernate/Session.html -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"
             p:dataSource-ref="dataSource"
             p:configLocation="${hibernate.config}"
             p:packagesToScan="fi.java.elearning"/>

<!-- Declare a datasource that has pooling capabilities-->   
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
            destroy-method="close"
            p:driverClass="${app.jdbc.driverClassName}"
            p:jdbcUrl="${app.jdbc.url}"
            p:user="${app.jdbc.username}"
            p:password="${app.jdbc.password}"
            p:acquireIncrement="5"
            p:idleConnectionTestPeriod="60"
            p:maxPoolSize="100"
            p:maxStatements="50"
            p:minPoolSize="10" />

<!-- Declare a transaction manager-->
<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" 
            p:sessionFactory-ref="sessionFactory" />

<!-- Enable annotation style of managing transactions -->
<tx:annotation-driven transaction-manager="transactionManager" />   

The System.out.println() I used in the controller shows (as far as I think) the problem:

Extract from Console:

ROLE ITEM: ID=1 | NAME=ACCOUNT_MANAGER | HASH=fi.java.elearning.data.model.Role@9293709
ROLE SERVICE: ID=1 | NAME=ACCOUNT_MANAGER | HASH=fi.java.elearning.data.model.Role@2721e92
ROLE SERVICE: ID=2 | NAME=Test | HASH=fi.java.elearning.data.model.Role@1235047f

So ITEM is the Role that was originally checked when I added the Account. And this one should be already checked when I edit the account. But it isnt. And I think it has to do with the different Address/Reference (9293709/2721e92). If I add a @Transcational Annotation on top of the controller action then the references are right. The checkbox is then checked. But that makes no sense to me. Transaction Annotations should be in the ServiceLayer, dont they?

Thanks a lot for your help...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

嗼ふ静 2024-11-23 17:17:14

您获得不同对象实例的原因是因为您在两个不同的事务中加载它们,因此有两个不同的持久性上下文。当您将 @Transactional 添加到控制器方法时,两个加载都发生在同一个事务中(在控制器中启动的事务),因此实例是相同的,因为它们来自相同的持久性上下文。

我个人不知道将控制器方法标记为事务性有一个大问题,但如果您不想这样做,您不能在 Role 对象上实现 equals() (和 hashCode()) 并回避它们是不同实例的事实?

The reason you get different object instances is because you are loading them in 2 different transactions, and thus two different persistence contexts. When you add @Transactional to the controller method, then both loads occur in the same transaction (the one started in the controller), and so the instances are identical because they come from the same persistence context.

I personally don't know that there is a big problem with marking a controller method as transactional, but if you didn't want to do that, couldn't you implement equals() (and hashCode()) on the Role object and get around the fact that they are different instances?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文