9

我正在使用 Spring + Hibernate + JPA,但我遇到了无法让我的实体持久保存到数据库的情况。我已经设置了一个使用@Transactional 注释的服务类。它使用包含注入的 EntityManager 的 DAO。当我在服务对象上调用该函数时,我看到 DAO 正在执行的读取的一堆选择,但由于我的 DAO 发出的合并和删除,没有更新/删除。我的设置肯定有问题,但我看不到。

持久性.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>

配置文件

<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.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);
    }

}
4

2 回答 2

20

好的,我发现了问题所在。我花了很长时间才弄清楚它与我的数据库配置无关,所以我想帮助有类似问题的人。

Spring 文档声明如下:

<tx:annotation-driven/>仅在定义它的同一应用程序上下文中的 bean 上查找 @Transactional。这意味着,如果您 <tx:annotation-driven/>为 DispatcherServlet 放入 WebApplicationContext,它只会检查控制器中的 @Transactional bean,而不是您的服务。有关详细信息,请参阅第 15.2 节,“DispatcherServlet”。

我原来的帖子中没有发布的是我的 servlet 定义,它有以下几行配置代码:

我的Servlet.xml

<context:annotation-config /> 
<context:component-scan base-package="com.myDomain.*" /> 

这会将所有带注释的 bean,包括控制器、服务和存储库,带入 servlet 上下文而不是应用程序上下文。这就是问题所在。当 Spring 查找带有 @Transactional 注释的 bean 时(由于<tx:annotation-driven/>在我的 config.xml 文件中存在),它正在应用程序上下文中查找它们。而且,根据我在上一个线程中发布的配置,没有 bean 被加载到我的应用程序上下文中……它们都在 servlet 上下文中。因此,当我的 servlet 调用带有 @Service 和 @Transactional 注释的 bean 时,它使用的 bean 没有被事务代理包装。因此,没有交易。诀窍(确切地说,正确的方法)是通过以下方式更改我的配置文件:

我的Servlet.xml

<context:annotation-config /> 
<context:component-scan base-package="com.myDomain.servlets" /> 

配置文件

<context:annotation-config /> 
<context:component-scan base-package="com.myDomain.dao" /> 
<context:component-scan base-package="com.myDomain.services" /> 

此配置确保所有控制器都存在于 servlet 上下文中,事务服务和存储库存在于应用程序上下文中,这是它们所属的位置。最后,经过许多不眠之夜,我的数据库写入仍然存在。

于 2011-06-28T18:27:36.600 回答
1

我们可以在 servlet-context.xml 中提供 Controller 如下

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

这将确保只有 Controller 仅存在于 servlet-context 中。在 root-context.xml 中使用以下

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

这将确保 Controller 以外的组件存在于应用程序上下文中。我搜索了很多这个解决方案,因为没有这个 JPA 不会更新数据库,希望这会对某人有所帮助

于 2015-08-22T04:00:40.717 回答