Michael Coxon
Michael Coxon

Reputation: 3535

Spring Boot: Data loading Application wont persist hibernate/JPA

My main aim is to make use of the Spring Boot application infrastructure as much as possible to read a file and persist some objects to the database, after a lot of processing. The problem is that the processing occurs, the DAO is found and an entityManager.save() is executed, but no objects are persisted (no hibernate SQL is run).

This is my loading tool application (likely to be wrong):

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.touchcorp.touchpoint"})
@Transactional
public class ContentImportingApplication {


    @Autowired
    private VoucherTemplateDao voucherTemplateDao;

    public static void main(String[] args) throws Exception {

        SpringApplication app = new SpringApplication(ContentImportingApplication.class);
         // ... customize app settings here
        ApplicationContext ctx = app.run(args);

        ContentImporter importer = new ContentImporter(ctx);

        importer.importContent();

        System.exit(0);
    }

    private static class ContentImporter {
        private ApplicationContext ctx = null;

        public ContentImporter(ApplicationContext ctx) {
            this.ctx = ctx;
        }

        public void importContent() throws Exception {
            String otc = readFileAsString("content-to-import");

            SAXBuilder sb = new SAXBuilder();
            Document jDom = sb.build(new StringReader(otc));

            // lots of processng code here .. this all works
            // ....

            Set<Long> productIds = productDocuments.keySet();
            VoucherTemplateDao dao = (VoucherTemplateDao) ctx.getBean("voucherTemplateDao");
            System.out.println("do we have a dao?: " + dao);
            for(Long productId : productIds) {

                VoucherTemplate vt = VoucherTemplate.make(productId, "RETAILER_GROUP:*|CHANNEL:*|LOCALE:de-AT|INDUSTRY:5499", VoucherTemplate.TemplateSchema.OTC);
                vt.setTemplate(productDocuments.get( productId ));
                // the thing that won't persist
                dao.save(vt);
            }
            jDom.getRootElement().addContent( new Element("w") );

        }

        private void findAndReplaceNestedDocs(Document jDom, Element documentInOtcContent) {
            // ...code omitted: this all works too...
        }

        public String readFileAsString(String name) {
            // ...code omitted: and so does this...

        }

    }

}

It makes use of various Configuration classes, but PersistenceConfig is, I think the most important:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@PropertySource({ "classpath:persistence.properties" })
@ComponentScan(basePackages = {"com.xxxxxcorp.xxxxxpoint.model"})
public class PersistenceConfig
{
    @Autowired
    private Environment env;

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
       LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
       em.setDataSource(dataSource());
       em.setPackagesToScan(new String[] { "com.xxxxxcorp.xxxxxpoint.model.domain" });

       JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
       em.setJpaVendorAdapter(vendorAdapter);
       em.setJpaDialect(jpaDialect());
       em.setJpaProperties(hibernateProperties());

       return em;
    }

    @Bean
    public DataSource dataSource()
    {
        // assumes dbcp
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
        dataSource.setUrl(env.getProperty("jdbc.url"));
        dataSource.setUsername(env.getProperty("jdbc.user"));
        dataSource.setPassword(env.getProperty("jdbc.pass"));
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(){
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory( this.entityManagerFactory().getObject() );
        transactionManager.setJpaDialect(this.jpaDialect());
        return transactionManager;
    }

    @Bean
    public JpaDialect jpaDialect() {
        return new HibernateJpaDialect();
    }


    @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation()
    {
        return new PersistenceExceptionTranslationPostProcessor();
    }

    Properties hibernateProperties() {
        return new Properties() {
            {
                setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
                setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
                setProperty("hibernate.globally_quoted_identifiers", "true");
            }
        };
    }

}

The associated properties file:

# jdbc
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/xxxxxxpoint?autoReconnect=true
jdbc.user=root
jdbc.pass=password

# hibernate
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=verify

As I say, there are no errors when all of this runs, just no persistence either. By the way, I have blessed the pom (under properties) with the main Application class:

<start-class>com.xxxxxcorp.xxxxxpoint.Application</start-class>

so I'm sure that I'm not starting up two contexts or two apps or anything else wierd like that. Can anyone tell me where I'm going wrong?

Upvotes: 0

Views: 814

Answers (2)

Michael Coxon
Michael Coxon

Reputation: 3535

I thought I'd post back the solution that worked. Basically the class that I said was wrong...was wrong. Thanks to David Syer for the answer.

Here's the amended ContentImportingApplication:

@Configuration
@EnableAutoConfiguration
@ComponentScan(basePackages = {"com.touchcorp.touchpoint"})
public class ContentImportingApplication {


    @Autowired
    private VoucherTemplateDao voucherTemplateDao;

    public static void main(String[] args) throws Exception {

        SpringApplication app = new SpringApplication(ContentImportingApplication.class);
        ApplicationContext ctx = app.run(args);

        ContentImporter importer = (ContentImporter) ctx.getBean("contentImporter");
        importer.importContent(ctx);

        System.exit(0);
    }

    @Bean
    public ContentImporter contentImporter()
    {
        return new ContentImporter();
    }


    public class ContentImporter {

        @Transactional
        public void importContent(ApplicationContext ctx) throws Exception {
            String otc = readFileAsString("content-to-import", ctx);

            SAXBuilder sb = new SAXBuilder();
            Document jDom = sb.build(new StringReader(otc));

            // first step: associate products to otc documents
            Map<Long,String> productDocumentNames = new HashMap<Long,String>();
            Map<Long,String> productDocuments = new HashMap<Long,String>();

            XPathExpression<Element> xpath = XPathFactory.instance().compile("//t", Filters.element());
            List<Element> ts = xpath.evaluate(jDom);

            for (Element t : ts) {
                String documentName = t.getAttributeValue("d");
                String productId = t.getChild("p").getChild("id").getValue();

                // put it in, if there is a voucher for this product
                if( documentName != null) {
                    productDocumentNames.put(Long.valueOf(productId), documentName);
                } else {
                    System.out.println("No voucher associated with productId: " + productId);
                }
            }

            List<Element> ds = jDom.getRootElement().getChildren("d");
            for (Element d : ds) {
                // for each document we find, we'll replace the document name with the content
                String documentName = d.getAttributeValue("k");

                Set<Long> productIds = productDocumentNames.keySet();
                for(Long productId : productIds) {

                     if( productDocumentNames.get( productId ).equals( documentName ) ) {

                         findAndReplaceNestedDocs( jDom, d );

                         productDocuments.put( productId, new XMLOutputter( ).outputString( d.getContent() ) );
                     }
                }
            }

            Set<Long> productIds = productDocuments.keySet();
            for(Long productId : productIds) {

                VoucherTemplate vt = VoucherTemplate.make(productId, "RETAILER_GROUP:*|CHANNEL:*|LOCALE:de-AT|INDUSTRY:5499", VoucherTemplate.TemplateSchema.OTC);
                vt.setTemplate(productDocuments.get( productId ));
                voucherTemplateDao.save(vt);
            }
            jDom.getRootElement().addContent( new Element("w") );

        }

        private void findAndReplaceNestedDocs(Document jDom, Element documentInOtcContent) {
            // find the nested docs
            List<Element> directives = documentInOtcContent.getChildren("w");
            // and replace them
            for(Element directive : directives) {
                List<Element> docs = jDom.getRootElement().getChildren("d");
                for (Element doc : docs) {
                    if (directive.getAttributeValue("d").equals(doc.getAttributeValue("k"))) {

                        List<Element> docContents = doc.getChildren();
                        List<Element> clones = new ArrayList<Element>();
                        for(Element docContent : docContents) {
                            Element docContentCopy = docContent.clone();
                            docContentCopy.detach();
                            clones.add(docContentCopy);
                        }

                        if (documentInOtcContent.indexOf(directive) != -1) {
                            documentInOtcContent.setContent(documentInOtcContent.indexOf(directive), clones);
                        }

                    }
                }
                break;
            }
        }

        public String readFileAsString(String name, ApplicationContext ctx) {
            String output = null;

            // working code in here
            return output;
        }

    }

}

Upvotes: 0

Dave Syer
Dave Syer

Reputation: 58094

There's no transaction active when your importer runs. You should make it a @Bean and mark its importContent() method @Transactional. Call the method from a CommandlineRunner maybe.

Also, if your "main aim is to make use of the Spring Boot application infrastructure as much as possible", then why don't you throw away the rest of your JPA and DataSource configuration (it's duplicated pretty much byte for byte in Boot)?

Upvotes: 2

Related Questions