Spring MVC 3 + Hibernate: Different Addresses of objects (once loaded via service, once via entity)
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...
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?
精彩评论