【问题标题】:Spring declarative transactions beginning after service methods return服务方法返回后开始的 Spring 声明性事务
【发布时间】:2012-01-09 16:10:38
【问题描述】:

我一直在通过使用以下方式实现 JavaEE Web 应用程序来自学 Spring 3.0:

  • 雄猫 7
  • Spring 3.0、安全 3.1
  • JPA:EclipseLink 2.3.1
  • MySQL 5.5,连接器/J 5.1

我的问题是:

  • 我已经使用 Spring AOP 在我的服务层上设置了声明式事务管理。
  • (我还有一个 Spring AOP 日志拦截器,在同一个地方声明。)
  • 创建新实体不是问题。
  • 更新现有实体!
  • 根据日志,服务方法事务开始之前返回!

    [org.springframework.orm.jpa.JpaTransactionManager] - <Using transaction object [org.springframework.orm.jpa.JpaTransactionManager$JpaTransactionObject@335f5e3a]>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Creating new transaction with name [org.snowjak.livesavegive.data.service.TagService.rename]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Opened new EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@3a234c2f] for JPA transaction>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Exposing JPA transaction as JDBC transaction [SimpleConnectionHandle: com.mysql.jdbc.JDBC4Connection@49116f61]>
    [myproject.data.service.MyEntityService] - <Beginning method: rename>
    [myproject.data.dao.MyEntityDao] - <Beginning method: get>
    [myproject.data.dao.MyEntityDao] - <After method: get>
    [myproject.data.dao.AbstractDao] - <Beginning method: update>
    [myproject.data.dao.AbstractDao] - <After method: update>
    [myproject.data.service.MyEntityService] - <After method: rename>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Triggering beforeCommit synchronization>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Triggering beforeCompletion synchronization>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Initiating transaction commit>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@3a234c2f]>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Triggering afterCommit synchronization>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Triggering afterCompletion synchronization>
    [org.springframework.orm.jpa.JpaTransactionManager] - <Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@3a234c2f] after transaction>
    
  • 如果我将 DAO 设为事务性而不是服务层,则无法更新的问题就会消失。

  • 但当局说那是不好的作风。

任何人都可以提供公会的任何指针/提示/技巧/秘密吗?

编辑:

我已经在我的记录器中为 org.springframework 开启了DEBUG detail;并看到,据我所知,事务毕竟得到了正确处理——即,在相关服务方法之前开始和之后结束。

我仍然有更新问题,即。我的更新没有出现在 PU 和 DB 中;现在为什么会这样? ...

编辑:

在尝试更新时,我还查看了 MySQL 通用日志。 JPA 似乎根本没有向数据库发出UPDATEs!

================================================ ====================================

实体:

  • myproject.data.entity 下。没什么特别的。通用 JPA。

    @Entity
    public class MyEntity implements Serializable {
        @Id
        @GeneratedValue(strategy=GenerationType.AUTO)
        int id;
    
        @Column(nullable=false, unique=true)
        String name;
    
        ...
    }
    

DAO:

myproject.data.dao下的接口。

  • interface AbstractDao

    @Repository
    interface AbstractDao<T,K> extends Serializable {
        public T get(K key);
        public Collection<T> getAll();
    
        public T save(T entity);
        public T update(T entity);
    }
    
  • interface MyEntityDao

    @Repository
    interface MyEntityDao extends AbstractDao<MyEntity, Integer> {
        public MyEntity get(String name);
    }
    

myproject.data.dao.jpa下实现。

  • abstract class AbstractDaoJpa

    @Repository
    abstract class AbstractDaoJpa implements AbstractDao<T,K> {
        protected EntityManager em;
        @PersistenceContext
        public void setEntityManager(EntityManager entityManager) {
            em = entityManager;
        }
    
        public T save(T entity) {
            em.persist(entity);
            return entity;
        }
    
        public T update(T entity) {
            return em.merge(entity);
        }
    }
    
  • class MyEntityDaoJpa extends AbstractDaoJpa implements MyEntityDao

    @Repository
    class MyEntityDaoJpa extends AbstractDaoJpa<MyEntity,Integer> implements MyEntityDao {
        @Override
        public MyEntity get(Integer key) {
            return em.find(MyEntity.class, key);
        }
    
        @Override
        public MyEntity get(String name) {
            return em.createQuery("select E from MyEntity E where E.name = :name", MyEntity.class).setParameter("name", name).getSingleResult();
        }
    
        ...
    }
    

服务层:

myproject.data.service下的接口

  • interface EntityService

    @Service
    public interface EntityService<T> extends Serializable {
        public Collection<T> getAll();
    }
    
  • interface MyEntityService extends EntityService

    @Service
    public interface MyEntityService extends EntityService<MyEntity> {
        public MyEntity create(String name);
        public MyEntity get(String name);
    
        public MyEntity rename(String name);
    
        ...
    }
    

myproject.data.service.impl下实现

  • class MyEntityServiceImpl implements MyEntityService

    @Service
    public class MyEntityServiceImpl implements MyEntityService {
        private MyEntityDao dao;
    
        public MyEntityServiceImpl(MyEntityDao newDao) {
            dao = newDao;
        }
    
        @Override
        public MyEntity create(String name) {
            MyEntity e = new MyEntity();
            e.name = name;
            ...
            return dao.save(e);
        }
    
        ...
    
        @Override
        public MyEntity rename(String oldName, String newName) {
            MyEntity e = dao.get(oldName);
            e.name = newName;
            dao.update(e); // An attempt to fix by explicitly merging the modified entity.
                           // Didn't fix the problem.
            return e;
        }
    
        ...
    }
    

弹簧配置:

...

[ Declaration of DAOs and Service beans. Appropriate DAOs are injected into Service beans. ]

...

<!-- Tomcat-managed DataSource lookup via JNDI -->
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/myDS"/>

<!-- Configure the EntityManagerFactory -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="loadTimeWeaver">
        <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
    </property>
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter"/>
    </property>
</bean>

<!-- Create the Transaction Manager -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<!-- Configure the Transaction Manager for AOP -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- Some methods are read-only. -->
        <tx:method name="get*" read-only="true" propagation="SUPPORTS"/>
        <!-- All others, use defaults. -->
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

...

<!-- Configure Spring AOP -->
<aop:config>
    <aop:pointcut id="wholeProject" expression="within(myproject..*)"/>
    <aop:pointcut id="serviceLayerOperation" expression="execution(* myproject.data.service.*.*(..))"/>

    <!-- Configure transaction management. -->
    <aop:advisor advice-ref="txAdvice" order="1" pointcut-ref="serviceLayerOperation"/>

    <!-- Configure logging interceptor. -->
    ...
</aop:config>

【问题讨论】:

  • 日志表明事务在方法之前开始(暴露JPA事务...)并在(提交JPA事务...之后提交)。你观察到什么行为,你期望什么行为?此外,您的 get 方法实现采用 Integer,而接口的方法采用 String。
  • 你还没有向我们展示public MyEntity get(String name);的实现
  • 我的猜测是您正在重命名实体,其名称与其已有的名称相同。你为什么不向我们展示public MyEntity get(String name);的实现?
  • 【不知道怎么从昨天开始就没有看到你们的cmets了!我很抱歉!] public MyEntity get(String name) 只是创建一个 JPA 查询来按名称检索持久的 MyEntity
  • 我不是简单地分配与以前相同的名称。我已经使用 Eclipse 进行了跟踪,它看起来像 MyEntityService.rename() 在重命名适当的 MyEntity(在我的测试用例中从“nameA”到“name1”)之后返回; 但是事务提交后,什么都没有改变! (在 PU 或 DB 中)

标签: jpa eclipselink spring-aop spring-transactions


【解决方案1】:

问题在于您直接访问实体的字段,而不通过 getter/setter。

使用公共字段是一种非常糟糕的做法,因为它会破坏封装。 EclipseLink 似乎需要方法,可能是因为它的编织在于将字节码添加到方法中。通过直接访问这些字段,您必须绕过使 EclipseLink 工作的字节码添加。

【讨论】:

    猜你喜欢
    • 2016-01-03
    • 2011-05-19
    • 2013-02-07
    • 1970-01-01
    • 1970-01-01
    • 2015-03-12
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多