2
votes

I'm trying out JPA/Hibernate and Spring. I have a controller --> service --> DAO structure and I'm having some issues JPA. I think its transaction related, but can't find the problem.

I have a service annotated with @Service and @Transactional. I have a "removeAll()" method where I first call "findAll" and then, I iterate over the list to call "remove()" on the entity. When I call the remove method, I get a "Illegal Argument Exception: Removing a detached instance".

From what I red, I should only put the @Transactional annotation on my service class and my DAO should join the transaction somehow. If I put all the @Transaction stuff on the DAO class, everything works fine. But as I remember, I should only need the annotation on the service class. There's probably a configuration problem, but can't find it.

So if anybody could give a look, maybe you'll see it right away.

Here the code snippet of my Service class:

@Service("courseService")
@Transactional
public class CourseServiceImpl implements CourseService {
    @Autowired
    @Qualifier("courseTemplateDAO")
    private CourseTemplateDAO courseTemplateDAO;

    public Integer removeAllTemplates() {
        int removed = 0;

        List<CourseTemplate> courseTemplates = getCourseTemplateDAO().findAll();
        for (CourseTemplate currCourseTemplate : courseTemplates) {
            getCourseTemplateDAO().remove(currCourseTemplate);
            removed++;
        }

        return removed;
    }

    public CourseTemplateDAO getCourseTemplateDAO() {
        return courseTemplateDAO;
    }

    public void setCourseTemplateDAO(CourseTemplateDAO courseTemplateDAO) {
        this.courseTemplateDAO = courseTemplateDAO;
    }
}

The Generic DAO class, pretty standard:

public abstract class JPADAOImpl<T> extends JpaDaoSupport implements JPADAO<T> {
    private Class<T> entityClass;

    public JPADAOImpl() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
    }

    public void persist(T entity) {
        getJpaTemplate().persist(entity);
    }

    public void remove(T entity) {
        getJpaTemplate().remove(entity);
    }

    public T merge(T entity) {
        return getJpaTemplate().merge(entity);
    }

    public void refresh(T entity) {
        getJpaTemplate().refresh(entity);
    }

    public T flush(T entity) {
        getJpaTemplate().flush();
        return entity;
    }

    public T findById(long id) {
        return getJpaTemplate().find(entityClass, id);
    }

    public List<T> findAll() {
        List<T> res = getJpaTemplate().execute(new JpaCallback<List<T>>() {
            public List<T> doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createQuery("SELECT h FROM " + entityClass.getName() + " h");
                return q.getResultList();
            } 
        });

        return (List<T>) res;
    }

    public Integer removeAll() {
        return getJpaTemplate().execute(new JpaCallback<Integer>() {
            public Integer doInJpa(EntityManager em) throws PersistenceException {
                Query q = em.createQuery("DELETE FROM " + entityClass.getName() + " h");
                return q.executeUpdate();
            } 
        });
    }

    public Class<T> getEntityClass() {
        return entityClass;
    }
}

And my CourseTemplateDAO implementation class:

@Repository("courseTemplateDAO")
public class CourseTemplateDAOImpl extends JPADAOImpl<CourseTemplate> implements CourseTemplateDAO {

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    public CourseTemplateDAOImpl() {

    }

    @PostConstruct
    public void init() {
        super.setEntityManagerFactory(entityManagerFactory);
    }
}

My spring application-context configuration file:

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-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/task http://www.springframework.org/schema/task/spring-task-3.0.xsd">

<context:annotation-config />
<context:component-scan base-package="org.ksshi"/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<bean class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean" id="entityManagerFactory">
    <property name="persistenceUnitName" value="KSSHIPersistenceUnit"/>
</bean>

<bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="messageSource" class="org.ksshi.service.i18n.impl.I18NMessageSource"/>

And finally, my persistence configuration file:

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
         version="2.0">

<persistence-unit name="KSSHIPersistenceUnit" transaction-type="RESOURCE_LOCAL">
    <description>
        Persistence unit for the KSSHI application
    </description>

    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <class>org.ksshi.entity.CourseTemplate</class>
    <class>other classes</class>

    <properties>
        <property name="hibernate.hbm2ddl.auto" value="update"/>
        <property name="hibernate.show_sql" value="true"/>
        <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
        <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
        <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/ksshi"/>
        <property name="hibernate.connection.username" value="sa"/>
        <property name="hibernate.connection.password" value=""/>

        <property name="hibernate.c3p0.min_size" value="5"/>
        <property name="hibernate.c3p0.max_size" value="20"/>
        <property name="hibernate.c3p0.timeout" value="300"/>
        <property name="hibernate.c3p0.max_statements" value="50"/>
        <property name="hibernate.c3p0.idle_test_period" value="3000"/>
    </properties>
</persistence-unit>

4

4 Answers

2
votes

Try moving the transactional notation to the method definition, like (the code below is a working code already in production):

@Transactional
public void delete(User entity){
    userDAO.delete(entity);
}

@Transactional(readOnly=true)
public User findUserByUsername(String username){
    return getUserDAO().findOne(username);
}

also you can play with PROPAGATION see reference at Spring Documentation specially the required and nested features.

0
votes

All configurations looks correct. But I am not sure what abstraction you have made in JpaDaoSupport and from where you have taken the JPA template(getJpaTemplate()). I think instead of injecting entitymanager in CourseTemplateDAOImpl class you can have that configured in JPADAOImpl as it is generic to your all DAO implementations.

I have modified the JPADAOImpl class and also added generic argument for the primary key(you have assumed it always to be long type)

public abstract class JPADAOImpl<T, PK extends Serializable> implements JPADAO<T, PK> {
    private Class<T> entityClass;

    @PersistenceContext(type=PersistenceContextType.TRANSACTION)
    protected EntityManager entityManager;

    @SuppressWarnings("unchecked")
    public JPADAOImpl() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
    }

    public void persist(T entity) {
        entityManager.persist(entity);
    }

    public void remove(T entity) {
        entityManager.remove(entity);
    }

    public T merge(T entity) {
        return entityManager.merge(entity);
    }

    public void refresh(T entity) {
        entityManager.refresh(entity);
    }

    public T flush(T entity) {
        entityManager.flush();
        return entity;
    }

    public T findById(PK id) {
        return entityManager.find(getEntityClass(), id);
    }

    @SuppressWarnings("unchecked")
    public List<T> findAll() {
        String all = "select h from " + getEntityClass().getSimpleName() + " h";
        Query query = entityManager.createQuery(all);
        return (List <T>)query.getResultList();
    }

    public Integer removeAll() {
        Query q = entityManager.createQuery("DELETE FROM " + getEntityClass().getName() + " h");
        return q.executeUpdate();
    }

    public Class<T> getEntityClass() {
        return entityClass;
    }
}

JPADAO interface

public interface JPADAO<T, PK extends Serializable> {

    void persist (T entity);

    void remove(T entity);

    T merge(T entity);

    void refresh(T entity);

    T flush(T entity);

    T findById(PK id);

    List<T> findAll();

    T update(T entity);
}

CourseTemplateDAOImpl class

@Repository("courseTemplateDAO")
public class CourseTemplateDAOImpl extends JPADAOImpl<CourseTemplate, long> implements CourseTemplateDAO {


}
0
votes

How does your CourseTemplate look like? Maybe you have lazy relationships and at the same time a CascadeType.ALL in that relationship? Maybe you should fetch elements in your findAll so they won't be detached?

0
votes

You are correct in saying that we prefer a service layer @Transactional methods over DAO layer ones. I would suggest changing transaction isolation levels and Transaction propagation

Changing to nested should work.

Better still, place @Transactional annotation on methods. In your case

@Transactional public Integer removeAllTemplates(){
...
}