Results 1 to 3 of 3

Thread: PersistenceExceptionTranslationPostProcessor

  1. #1

    Default 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. #2
    Join Date
    Apr 2007
    Posts
    307

    Default

    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.
    Chris Beams
    Spring Framework committer, VMware
    http://github.com/cbeams

  3. #3

    Default

    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...

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •