Entities Not Persisting - Spring + Hibernate + JPA
I'm using Spring + Hibernate + JPA and I have a situation where I can't get my entities to persist to the database. I've set up a service class that is annotated with @Transactional. It uses a DAO that contains an injected EntityManager. When I call the function on the service object I see a bunch of selects for the reads the DAO is doing, but no updates/deletes as a result of merges and removes issued by my DAO. Surely there's something wrong with my setup, but I can't see it.
persistence.xml
<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_1_0.xsd"
version="1.0">
<persistence-unit name="pu">
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.InformixDialect" />
<property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider" />
<property name="hibernate.showsql" value="true" />
<property name="hibernate.cache.use_second_level_cache"
value="false" />
</properties>
</persistence-unit>
config.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.0.3.xsd
http://www.开发者_JAVA技巧springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver" />
<property name="url" value="jdbc:postgresql://localhost:5432/testdb" />
<property name="username" value="username" />
<property name="password" value="password" />
</bean>
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="pu" />
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="showSql" value="true" />
<property name="databasePlatform" value="org.hibernate.dialect.PostgreSQLDialect" />
</bean>
</property>
<property name="loadTimeWeaver">
<bean
class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
</property>
</bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"
p:entityManagerFactory-ref="entityManagerFactory" />
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
<context:annotation-config/>
</beans>
AccountService.java
@Service("accountService")
@Transactional(propagation=Propagation.REQUIRED)
public class AccountService {
private static final Logger log = Logger.getLogger(AccountService.class);
@Autowired
private UserDAO userDAO;
public void activateUser(String username, String activationCode) {
PendingActivation pendingActivation = userDAO.getPendingActivation(
username, activationCode);
Client client = pendingActivation.getClient();
if (!userDAO.removePendingActivation(pendingActivation)) {
log.warn("Unable to remove pending activation");
}
if (!userDAO.enableUser(client)) {
log.error("Unable to enable client");
return;
}
return;
}
}
UserDAOImpl.java
@Repository("userDAO")
public class UserDAOImpl implements UserDAO, Serializable {
private static final long serialVersionUID = 1L;
private static Logger log = Logger.getLogger(UserDAOImpl.class);
@PersistenceContext
EntityManager em;
@Override
public PendingActivation getPendingActivation(String username, String activationCode) {
Query q = em.createNamedQuery("getActivationCode")
.setParameter("activationCode", activationCode);
PendingActivation pendingActivation = null;
try {
pendingActivation = (PendingActivation)q.getSingleResult();
return pendingActivation;
}
catch (Exception e) {
log.warn("Could not retrieve activation code " + activationCode + " for user " + username, e);
return null;
}
}
@Override
public boolean enableUser(Client client) {
try {
client.setEnabled(true);
client = em.merge(client); // this never generates an update
}
catch(Exception e) {
log.error("Unable to enable client: " + client.getUsername(), e);
return false;
}
return true;
}
@Override
public boolean removePendingActivation(PendingActivation pendingActivation) {
try {
pendingActivation = (PendingActivation)em.getReference(PendingActivation.class, pendingActivation.getPendingActivationId());
em.remove(pendingActivation); // this never generates a delete
}
catch(Exception e) {
log.warn("Unable to remove activation: " + pendingActivation.getActivationCode(), e);
return false;
}
return true;
}
}
AccountActivationController.java
@Controller
public class AccountActivationController {
@Autowired
@Qualifier("accountService")
AccountService accountService;
@RequestMapping("activate.do")
public String doActivate(
@RequestParam("activationCode") String activationCode,
@RequestParam("username") String username,
ModelMap model) {
UnitCriteria unitCriteria = accountService.activateUser(username, activationCode);
if (unitCriteria == null) {
return "account/activationError";
}
model.addAttribute("fromActivation", true);
return "forward:search.do?" + unitCriteria.toUrlParams(true);
}
}
Ok, I figured out the problem. It took me forever to figure it out and had nothing to do with my database config, so I want to help people who have similar issues.
The Spring documentation states the following:
<tx:annotation-driven/>
only looks for @Transactional on beans in the same application context it is defined in. This means that, if you put<tx:annotation-driven/>
in a WebApplicationContext for a DispatcherServlet, it only checks for @Transactional beans in your controllers, and not your services. See Section 15.2, “The DispatcherServlet” for more information.
What's not posted in my original post is my servlet definition, which has the following lines of configuration code:
myServlet.xml
<context:annotation-config />
<context:component-scan base-package="com.myDomain.*" />
This brings all annotated beans, including Controllers, Services and Repositories, into the servlet context instead of the application context. And therein lies the problem. When Spring looks for beans annotated with @Transactional (due to the existence of <tx:annotation-driven/>
in my config.xml file) it is looking for them in the application context. And, based on my config that was posted in my previous thread, there are no beans being loaded into my application context... they're all in the servlet context. Therefore, when my servlet was calling the beans annotated with @Service & @Transactional it was using beans that were not wrapped by transaction proxies. Thus, no transactions. The trick (rather, the correct way) was to change my config files in the following way:
myServlet.xml
<context:annotation-config />
<context:component-scan base-package="com.myDomain.servlets" />
config.xml
<context:annotation-config />
<context:component-scan base-package="com.myDomain.dao" />
<context:component-scan base-package="com.myDomain.services" />
This configuration ensures that all Controllers exist in the servlet context and Transactional Services and Repositories exist in the application context, which is where they belong. And finally, after many sleepless nights, my database writes are persisting.
We can provide the Controller in servlet-context.xml as follows
<context:component-scan base-package="com.myDomain" use-default-filters="false" >
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
This will ensure only Controller exists only in servlet-context. In root-context.xml use the following
<context:component-scan base-package="com.myDomain">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
This will ensure the components other than Controller exists in application context. I searched for this solution a lot, as without this JPA was not updating the database, hope this will help someone
精彩评论