3

I know this problem is asked a lot but I am stuck. I am basing my project off of this tutorial: http://www.cavalr.com/blog/Spring_3_and_Annotation_Based_Hibernate_4_Example

This is my root-context.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:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx.xsd">

  <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="initialPoolSize" value="1" />
    <property name="minPoolSize" value="1" />
    <property name="maxPoolSize" value="20" />
  </bean>

  <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
      <list>
        <value>classpath:db.properties</value>
      </list>
    </property>
  </bean>

  <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="packagesToScan" value="com.joe.recipes.data" />
    <property name="hibernateProperties">
      <props>
        <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop>
        <prop key="hibernate.show_sql">true</prop>
      </props>

    </property>
  </bean>

  <!-- Enables the Hibernate @Transactional programming model -->
  <tx:annotation-driven transaction-manager="transactionManager"/>

  <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>
</beans>

This is my servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:context="http://www.springframework.org/schema/context"
             xmlns:mvc="http://www.springframework.org/schema/mvc"
             xsi:schemaLocation="http://www.springframework.org/schema/mvc 
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context   
    http://www.springframework.org/schema/context/spring-context.xsd">

  <!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
  <context:component-scan base-package="com.joe.recipes" />
  <!-- Enables the 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 -->
  <resources mapping="/resources/**" location="/resources/" />

  <!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
  <beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <beans:property name="prefix" value="/WEB-INF/views/" />
    <beans:property name="suffix" value=".jsp" />
  </beans:bean>

</beans:beans>

This is my AbstractDaoImpl

public abstract class AbstractDaoImpl<E, I extends Serializable> implements AbstractDao<E,I> {

    private Class<E> entityClass;

    protected AbstractDaoImpl(Class<E> entityClass) {
        this.entityClass = entityClass;
    }

    @Autowired
    private SessionFactory sessionFactory;

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

    @SuppressWarnings("unchecked")
    @Override
    public E findById(I id) {
        return (E) getCurrentSession().get(entityClass, id);
    }

    @Override
    public void saveOrUpdate(E e) {
        getCurrentSession().saveOrUpdate(e);
    }

    @Override
    public void delete(E e) {
        getCurrentSession().delete(e);
    }

    @Override
    public List findByCriteria(Criterion criterion) {
        Criteria criteria = getCurrentSession().createCriteria(entityClass);
        criteria.add(criterion);
        return criteria.list();
    }
}

This is my RecipeDaoImpl class

@Repository
public class RecipeDaoImpl extends AbstractDaoImpl<Recipe, String> implements RecipeDao {

    protected RecipeDaoImpl() {
        super(Recipe.class);
    }

    @Override
    public boolean saveRecipe(Recipe r) {
        return saveRecipe(r);
    }

    @Override
    public Recipe getRecipe(String recipeId) {
        return findById(recipeId);
    }

    @SuppressWarnings("unchecked")
    @Override
    public List<Recipe> findRecipes(String keyword) {
        return findByCriteria( Restrictions.and( Restrictions.like("name", keyword, MatchMode.ANYWHERE),
            Restrictions.like("keywords", keyword, MatchMode.ANYWHERE) ) );
    }
}

This is my RecipeServiceImpl class

@Service("recipeService")
@Transactional(readOnly = true)
public class RecipeServiceImpl implements RecipeService {
    @Autowired
    private RecipeDao recipeDao;

    @Override
    @Transactional(readOnly = false)
    public boolean saveRecipe(Recipe r) {
        return recipeDao.saveRecipe(r);
    }

    @Override
    public Recipe getRecipe(String recipeId) {
        return recipeDao.getRecipe(recipeId);
    }

    @Override
    public List<Recipe> findRecipes(String keyword) {
        return recipeDao.findRecipes(keyword);
    }
}

This is my RecipeController

/**
 * Handles requests for the application home page.
 */
@Controller
public class RecipeController {

    private static final Logger logger = LoggerFactory.getLogger(RecipeController.class);

    @Autowired
    private RecipeService recipeService;

    /**
     * Adds recipes to the DB
     */
    @RequestMapping(value = "/add", method = RequestMethod.GET)
    public String add(Locale locale, Model model) {
        return "add";
    }

    /**
     * Searches for recipes
     */
    @RequestMapping(value = "/search", method = RequestMethod.POST)
    public String search(@RequestParam(value="keyword", required=true) String keyword, Model model) {

        List<Recipe> recipes = recipeService.findRecipes(keyword);
        System.out.println( "Results:"+ recipes.size() );
        return "results";
    }

    /**
     * Logs in the user
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public String login(Locale locale, Model model) {

        return "login";
    }
}

I've tried putting the @Transactional on the RecipeDaoImpl class and the AbstractDaoImpl class and neither worked.

EDIT: I fixed this by catching the exception and opening a new one:

public Session getCurrentSession() {
    Session session = null;
    try { 
        session = sessionFactory.getCurrentSession();
    } catch ( HibernateException he ) {
        session = sessionFactory.openSession();
    }
    return session;
}
JamesENL
  • 6,400
  • 6
  • 39
  • 64
javajoe316
  • 151
  • 1
  • 2
  • 11

2 Answers2

0

What does it mean "neither worked"? What's your problem?

I think your problem is @EnableTransactionManagement and only looks for @Transactional on beans in the same application context they are defined in. This means that, if you put annotation driven configuration in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services.

Try, in your application-context.xml

<context:component-scan base-package="path.to.your.models, path.to.your.daos, path.to.your.services"/>

In your servlet-context.xml

<context:component-scan base-package="com.springhibernatejpatest.controllers"/>

Another thread: Spring @transactional not working

Community
  • 1
  • 1
alfcope
  • 2,327
  • 2
  • 13
  • 21
0

Your "fix" is actually a dangerous one, as you are opening a new session outside the scope of Springs transaction management. This will eventually leave to connection leaks, memory issues and stability issues.

The real problem is that you have <tx:annotation-driven /> in your root context and you do component scanning the context loaded by the DispatcherServlet. Move the <tx:annotation-driven /> to the context loaded by the DispatcherServlet to really fix your problem.

Or split component-scanning into 2, the root context should scan everything but your controller

<context:component-scan base-package="com.joe.recipes">
  <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan>

, and the dispatcher servlet one should detect only web related beans.

<context:component-scan base-package="com.joe.recipes" use-default-filters="false">
  <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> 
</context:component-scan>

Now you can leave the <tx:annotation-driven /> where it is.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224