I've been following this tutorial: http://www.scribd.com/doc/25244173/Java-Struts-Spring-Hibernate-Tutorial The setup (described ahead) was working fine with the tutorial files, but when I have made the changes - delete / update actions just don't happen. No errors or quirks, it just completely ignores me! As for retrieving data - everything works perfectly..
Almost all the files from the tut are the same, with these differences; The tutorial uses a services file:
Services.java
package services;
import org.springframework.transaction.annotation.Transactional;
import org.hibernate.SessionFactory;
import org.hibernate.Session;
import data.*;
import java.util.List;
// This class is the business services tier in the application.
// @Transactional is needed so that a Hibernate transaction is set up,
// otherwise updates won't have an affect
@Transactional
public class Services {
// So Spring can inject the session factory
SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory value) {
sessionFactory = value;
}
// Shortcut for sessionFactory.getCurrentSession()
public Session sess() {
return sessionFactory.getCurrentSession();
}
public Event getEventById(long id) {
return (Event) sess().load(Event.class, id);
}
public Person getPersonById(long id) {
return (Person) sess().load(Person.class, id);
}
public void deleteEventById(long id) {
sess().delete(getEventById(id));
}
public void deletePersonById(long id) {
sess().delete(getPersonById(id));
}
public void createEvent(String name) {
Event theEvent = new Event();
theEvent.setName(name);
sess().save(theEvent);
}
public void createPerson(String name) {
Person p = new Person();
p.setName(name);
sess().save(p);
}
@SuppressWarnings("unchecked")
public List getEvents() {
return sess().createQuery("from Event").list();
}
@SuppressWarnings("unchecked")
public List getPeople() {
return sess().createQuery("from Person").list();
}
public void removePersonFromEvent(int personId, int eventId) {
getEventById(eventId).getPeople().remove(getPersonById(personId));
}
public void addPersonToEvent(int personId, int eventId) {
getEventById(eventId).getPeople().add(getPersonById(personId));
}
}
and I have tried to separate the files by using a parent controller and static calls to HibernateUtil: HibernateUtil.java
package com.epa.util;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class HibernateUtil {
// So Spring can inject the session factory
static SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory value) {
sessionFactory = value;
}
// Shortcut for sessionFactory.getCurrentSession()
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}
BaseController.java
package com.epa.controller.base;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.transaction.annotation.Transactional;
import com.epa.controller.EventController;
import com.epa.controller.PersonController;
import com.epa.util.HibernateUtil;
@Transactional
public class BaseController {
protected SessionFactory sessionFactory = HibernateUtil.getSessionFactory();
// Shortcut for sessionFactory.getCurrentSession()
public Session sess() {
return sessionFactory.getCurrentSession();
}
private PersonController personController = null;
private EventController eventController = null;
public PersonController getPersonController() {
if (this.personController == null) {
this.personController = new PersonController();
}
return personController;
}
public EventController getEventController() {
if (this.eventController == null) {
this.eventController = new EventController();
}
return eventController;
}
}
EventController.java
package com.epa.controller;
import java.util.List;
import org.springframework.transaction.annotation.Transactional;
import com.epa.controller.base.BaseController;
import com.epa.model.Event;
@Transactional
public class EventController extends BaseController {
public Event getEventById(long id) {
return (Event) sess().load(Event.class, id);
}
public void deleteEventById(long id) {
sess().delete(getEventById(id));
}
public void createEvent(String name) {
Event theEvent = new Event();
theEvent.setName(name);
sess().save(theEvent);
}
@SuppressWarnings("unchecked")
public List getEvents() {
return sess().createQuery("from Event").list();
}
}
and spring's applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<!-- The singleton hibernate session factory -->
<bean id="sessionFactory" scope="singleton"
class="org.springfr开发者_如何学编程amework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="configLocation" value="classpath:hibernate.cfg.xml" />
</bean>
<!-- Spring's hibernate transaction manager, in charge of making hibernate sessions/txns -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<!-- So classes/functions with @Transactional get a hibernate txn -->
<tx:annotation-driven />
<!-- Inject my business services class to the actions
<bean id="services" class="com.epa.services.Services" scope="singleton">
<property name="sessionFactory" ref="sessionFactory" />
</bean>-->
<!-- Inject my business services class to the actions -->
<bean id="hibernateUtil" class="com.epa.util.HibernateUtil" scope="singleton">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<bean id="baseController" class="com.epa.controller.base.BaseController" scope="singleton" />
</beans>
Your code looks as if you did not fully understand the concept of Spring dependency injection because your BaseController
does Spring's job of managing singletons and suppling dependencies.
Problem
You are creating EventController
and PersonController
instances by yourself inside the BaseController
instead of relying on Spring. Spring cannot intercept the manual creation of class instances and providing them with the transactional behavior which you require here by annotating the classes with the Spring annotation @Transactional
.
Solution
So, lets find a path to clean this code up. First remove the class HibernateUtil
because it is a mixture of static calls, java bean concepts and unused transactional behavior while it brings a new abstraction layer without any benefits. Remember to remove it from the applicationContext.xml
also.
Now remove the BaseController
from your applicationContext.xml
also and give it a major rewrite because it works as a Singleton-Factory for your PersonController
and EventController
which would correctly managed by Spring itself in a environment like this.
public abstract class BaseController {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
protected Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
This way BaseController
becomes an abstract base class for other extending classes, which can make use of the provided Hibernate Session
object. Now lets create a nice interface for your EventController
.
public interface EventController {
Event getEventById(long id);
void deleteEventById(long id);
Event createEvent(String name);
List getEvents();
}
Next we need the implementation of the above interface for Hibernate so let us call the new class EventControllerHibernate
making use of the earlier created BaseController
implementation.
public class EventControllerHibernate extends BaseController implements EventController {
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public Event getEventById(long id) {
return (Event) getCurrentSession().get(Event.class, id);
}
@Transactional(propagation = Propagation.REQUIRED)
public void deleteEventById(long id) {
getCurrentSession().delete(getEventById(id));
}
@Transactional(propagation = Propagation.REQUIRED)
public Event createEvent(String name) {
return (Event) getCurrentSession().save(new Event(name));
}
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
@SuppressWarnings("unchecked")
public List getEvents() {
return getCurrentSession().createQuery("from Event").list();
}
}
And remember to register this class in your Spring applicationContext.xml
correctly so that the required SessionFactory
is provided, too:
<bean id="eventController" class="com.epa.controller.EventControllerHibernate">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
If you retrieve a Spring bean of type EventController
from Spring you will get a transaction-aware proxy object fully implementing your EventController
interface delegating to the business logic implemented inside EventControllerHibernate
.
Remember: A new EventControllerHibernate()
should never occur in your application since this will not do the trick because Spring can not intercept the manual creation of a class instance! Getting a transaction-aware instance programmatically would look like this:
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
EventController eventController = context.getBean("eventController", EventController.class);
The controller codes are not very elegant. You should be following everything codescape has said here. However a simple fix to problem would be the following: Instead of returning a new PersonController() or new EventController(), you can return a bean instance of the same which have been injected into the BaseController(). This will return a proxy object on which the @Transactional can be intercepted by spring. However, as I said the controllers defined by are not good codes.
精彩评论