Announcement Announcement Module
Collapse

JavaConfig forum decommissioned in favor of Core Container

As described at

http://static.springsource.org/sprin...fig/README.TXT

key features of the Spring JavaConfig project have been migrated into the core Spring Framework as of version 3.0.

Please see the Spring 3.0 documentation on @Configuration and @Bean support:

http://static.springsource.org/sprin...tml#beans-java

For any questions related to @Configuration classes and @Bean methods in Spring 3.0, please post in the dedicated 'Core Container' forum at

http://forum.springsource.org/forumdisplay.php?f=26
See more
See less
PersistenceExceptionTranslationPostProcessor Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • PersistenceExceptionTranslationPostProcessor

    Hi, Chris!

    First of all thanks for the M4, is it a wonderful release. I only now got to check it out, and it rocks. I configured reasonably complicated application without a single XML. Love it.
    The only annoyance I encountered is the same old PersistenceExceptionTranslationPostProcessor.

    The following won't work:
    Code:
        @Bean
        public EntityManagerFactory entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            ...
            return (EntityManagerFactory) getObject(localContainerEntityManagerFactoryBean);
        }
    
        @Bean
        public JpaTransactionManager transactionManager() {
            return new JpaTransactionManager(entityManagerFactory());
        }
    
        @Bean
        public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
            return new PersistenceExceptionTranslationPostProcessor();
        }
    It fails, because PersistenceExceptionTranslationPostProcessor must find at least one PersistenceExceptionTranslator in the ApplicationContext. In the above code it is not the case, because the LocalContainerEntityManagerFactoryBean, which is the PersistenceExceptionTranslator is not exposed, but used internally in the @Bean method.

    So, let's try to expose the LocalContainerEntityManagerFactoryBean as a bean itself:
    Code:
        @Bean
        public LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean() {
            LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            ...
            return localContainerEntityManagerFactoryBean;
        }
    
        @Bean
        public EntityManagerFactory entityManagerFactory() {
            return (EntityManagerFactory) getObject(localContainerEntityManagerFactoryBean());
        }
    
        @Bean
        public JpaTransactionManager transactionManager() {
            return new JpaTransactionManager(entityManagerFactory());
        }
    
        @Bean
        public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
            return new PersistenceExceptionTranslationPostProcessor();
        }
    This looks reasonable, but fails with
    Code:
    java.lang.ClassCastException: $Proxy30 cannot be cast to org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
    There is a workaround, to cache localContainerEntityManagerFactoryBean in field variable and to use it in transactionManager creation.

    Besides, exposing LocalContainerEntityManagerFactoryBean as a @Bean registers entityManagerFactory bean implicitly, preventing me from declaring the EntityManagerFactory as a @Bean.
    The working solution looks like the following:

    Code:
        private LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean;
    
        @Bean
        public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
            LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
            ...
            this.localContainerEntityManagerFactoryBean =localContainerEntityManagerFactoryBean;
            return localContainerEntityManagerFactoryBean;
        }
    
        @Bean
        public JpaTransactionManager transactionManager() {
            return new JpaTransactionManager((EntityManagerFactory) getObject(localContainerEntityManagerFactoryBean));
        }
    
        @Bean
        public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
            return new PersistenceExceptionTranslationPostProcessor();
        }
    Note the local variable, the name of LocalContainerEntityManagerFactoryBean creation method and the creation of JpaTransactionManager.

    This is ugly.

  • #2
    Good points, for sure.

    Just for tracking purposes, would you create a JIRA issue about this? Feel free to suggest how you'd like first-class support for exception translation to look, but the main thing is just to express that the current support is a hassle and too low-level.

    Thanks.

    Comment


    • #3
      No prob, I'll open an issue. But before I'd like to pinpoint the problem.
      As I see it the biggest problem is that EntityManagerFactoryBean abuses the FactoryBean mechanism.

      Correct me if I am wrong, the purpose of FactoryBean is to allow property-based configuration for objects, which don't expose JavaBean-like setters.
      This is exactly the way you treat them in java-config. We create an instance of the FactoryBean, configure it, and then throw it away by taking the bean itself via getObject() in-place, inside the configuration method. The FactoryBean shouldn't be exposed to the ApplicationContext directly.
      EntityManagerFactoryBean ignores the nature of FactoryBean twice - first, it must be exposed to the ApplicationContext, because it is not just FactoryBean, but also a PersistenceExceptionTranslator. How comes that technical factory became first-class bean?

      Besides, it also registers beans in the ApplicationContext by itself! It is not of its business. All it has to do is to configure the bean, and wait for the ApplicationContext (or the java-config) to call getObject() on it.

      That's my view on the problem, and it is not directly java-config related.

      Considering the fix - take a look at the following:
      First of all let's make the code right again - as it should be
      Code:
          @Bean
          public EntityManagerFactory entityManagerFactory() {
              LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
              localContainerEntityManagerFactoryBean.setJpaDialect(jpaDialect());
              ...
              return (EntityManagerFactory) getObject(localContainerEntityManagerFactoryBean);
          }
      
          @Bean
          public HibernateJpaDialect jpaDialect() {
              return new HibernateJpaDialect();
          }
      
          @Bean
          public JpaTransactionManager transactionManager() {
              return new JpaTransactionManager(entityManagerFactory());
          }
      
          @Bean
          public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
              return new PersistenceExceptionTranslationPostProcessor();
          }
      Now let's suffice the PersistenceExceptionTranslationPostProcessor with our own custom-made PersistenceExceptionTranslator:
      Code:
      public class JpaPersistenceExceptionTranslator implements PersistenceExceptionTranslator {
          private JpaDialect jpaDialect;
      
          public JpaPersistenceExceptionTranslator(JpaDialect jpaDialect) {
              this.jpaDialect = jpaDialect;
          }
      
      
          @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
          public DataAccessException translateExceptionIfPossible(RuntimeException ex) {
              return (this.jpaDialect != null ? this.jpaDialect.translateExceptionIfPossible(ex) :
                      EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(ex));
          }
      }
      This is copy-paste from org.springframework.orm.jpa.AbstractEntityManagerF actoryBean
      Now I add it to the config:
      Code:
          @Bean
          public PersistenceExceptionTranslator persistenceExceptionTranslator(){
              return new JpaPersistenceExceptionTranslator(jpaDialect());
          }
      Looks amazing, isn't it?
      Well, almost, but it fails with very strange error:
      Code:
      Caused by: java.lang.IllegalStateException: Could not register object [org.apache.commons.dbcp.BasicDataSource@b3cac9] under bean name 'dataSource': there is already object [org.apache.commons.dbcp.BasicDataSource@b307f0] bound
      This is weird. I checked, I call new BasicDataSource only once. And I really don't see what it has to do with our dances with the PersistenceExceptionTranslationPostProcessor...

      Comment

      Working...
      X