Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JPA with Spring MVC Configured via Annotations

I am trying to create a Spring MVC application leveraging JPA for its persistence layer. Unfortunately, I was getting a NullPointerException when accessing the EntityManager as Spring does not appear to be injecting it. My configuration is all annotation-based with @EnableWebMvc. After some searching, I added @Transactional on my DAO and @EnableTransactionManagement on my @Configuration class. Then I got an error about not having a DataSource. Supposedly, a class with @EnableTransactionManagement needs to implement TransactionManagementConfigurer. However, I am having problems figuring out how to create the DataSource as well as why it cannot get it from my persistence.xml.

I would greatly appreciate any help in trying to get the EntityManager injected into my DAO.

My @Configuration class

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.example.myapp")
public class MvcConfig extends WebMvcConfigurerAdapter 
        implements TransactionManagementConfigurer {

private static final boolean CACHE_ENABLED = true;
private static final String TEMPLATE_PATH = "/WEB-INF/freemarker";
private static final String TEMPLATE_SUFFIX = ".ftl";

private static final Logger LOG = Logger.getLogger( MvcConfig.class );

@Override
public void addResourceHandlers( ResourceHandlerRegistry registry ) {
    registry.addResourceHandler( "/stylesheets/**" ).addResourceLocations( "/stylesheets/" );
}

@Bean
public FreeMarkerConfigurer configureFreeMarker() {
    final FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
    configurer.setTemplateLoaderPath( TEMPLATE_PATH );
    return configurer;
}

@Bean
public ViewResolver configureViewResolver() {
    final FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    resolver.setCache( CACHE_ENABLED );
    resolver.setSuffix( TEMPLATE_SUFFIX );
    return resolver;
}

@Bean
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
    return new DataSourceTransactionManager();
}

}

My DAO

@Component
@Transactional
public class MyDAO {

    private static final Logger LOG = Logger.getLogger( MyDAO.class );

    @PersistenceContext
    private EntityManager entityManager;

    public MyClass getMyClass() {
        LOG.debug( "getMyClass()" );
        final CriteriaQuery<MyClass> query = criteriaBuilder.createQuery( MyClass.class );
        // more code here, but it breaks by this point
        return myData;
    }

}

My Updated Code

I have reached the point in which it almost all works. The EntityManager is being injected properly. However, transactions are not working. I get errors if I try to use a RESOURCE_LOCAL approach so I am looking at JTA managed transactions. When I add @Transactional on any of my DAO methods, I get a "Transaction marked for rollback" error with no further details in any log files to assist troubleshooting. If I remove the annotation from a basic read-only select, the select will work perfectly fine (not sure if I should even be putting the annotation on select-only methods). However, I obviously need this working on methods which perform db writes. If I debug through the code, it seems to retrieve the data perfectly fine. However, as it returns from the method, the javax.transaction.RollbackException gets thrown. From my understanding of everything, it seems as if the exception occurs in the AOP post-method processing.

My @Configuration class

@Configuration
@EnableWebMvc
@EnableTransactionManagement
@ComponentScan("com.example.myapp")
public class MvcConfig extends WebMvcConfigurerAdapter {

private static final boolean CACHE_ENABLED = true;
private static final String TEMPLATE_PATH = "/WEB-INF/freemarker";
private static final String TEMPLATE_SUFFIX = ".ftl";

private static final Logger LOG = Logger.getLogger( MvcConfig.class );

@Override
public void addResourceHandlers( ResourceHandlerRegistry registry ) {
    registry.addResourceHandler( "/stylesheets/**" ).addResourceLocations( "/stylesheets/" );
}

@Bean
public FreeMarkerConfigurer configureFreeMarker() {
    final FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
    configurer.setTemplateLoaderPath( TEMPLATE_PATH );
    return configurer;
}

@Bean
public ViewResolver configureViewResolver() {
    final FreeMarkerViewResolver resolver = new FreeMarkerViewResolver();
    resolver.setCache( CACHE_ENABLED );
    resolver.setSuffix( TEMPLATE_SUFFIX );
    return resolver;
}

@Bean
public PlatformTransactionManager transactionManager() {
    return new JtaTransactionManager();
}

@Bean
public AbstractEntityManagerFactoryBean entityManagerFactoryBean() {
    LocalEntityManagerFactoryBean factory = new LocalEntityManagerFactoryBean();
    factory.setPersistenceUnitName( "my_db" );
    return factory;
}

}
like image 707
Marshmellow1328 Avatar asked Feb 10 '13 17:02

Marshmellow1328


People also ask

Can we use Spring JPA with Spring MVC?

In this Java Spring tutorial, you will learn how to configure a Spring MVC application to work with Spring Data JPA by developing a sample web application that manages information about customers.

Can we use JPA repository in Spring MVC?

Spring Data JPA Repository ConfigurationTo activate the Spring JPA repository support, we can use the @EnableJpaRepositories annotation and specify the package that contains the DAO interfaces: @EnableJpaRepositories(basePackages = "com. baeldung. spring.

Why we use JPA instead of Spring MVC?

JPA handles most of the complexity of JDBC-based database access and object-relational mappings. On top of that, Spring Data JPA reduces the amount of boilerplate code required by JPA. That makes the implementation of your persistence layer easier and faster.

What are the annotation used in Spring MVC?

Spring MVC Annotations@PathVariable. @RequestParam. @ModelAttribute. @RequestBody and @ResponseBody.


1 Answers

In my application I didn't implement TransactionManagerConfigurer interface. I use next code to configure JPA (with Hibernate implementation). You can do the same in your configuration class.

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
        LocalContainerEntityManagerFactoryBean factoryBean = 
                new LocalContainerEntityManagerFactoryBean();

        factoryBean.setDataSource(dataSource());
        factoryBean.setPackagesToScan(new String[] {"com.dimasco.springjpa.domain"});

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setShowSql(true);
        //vendorAdapter.setGenerateDdl(generateDdl)

        factoryBean.setJpaVendorAdapter(vendorAdapter);

        Properties additionalProperties = new Properties();
        additionalProperties.put("hibernate.hbm2ddl.auto", "update");

        factoryBean.setJpaProperties(additionalProperties);


        return factoryBean;
    }

    @Bean
    public DataSource dataSource() {
        final ComboPooledDataSource dataSource = new ComboPooledDataSource();

        try {
            dataSource.setDriverClass(driverClass);
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

        dataSource.setJdbcUrl(jdbcUrl);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        dataSource.setMinPoolSize(3);
        dataSource.setMaxPoolSize(15);
        dataSource.setDebugUnreturnedConnectionStackTraces(true);

        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactoryBean().getObject());

        return transactionManager;
    }

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

Hope this will help you)

EDIT:

You can get datasource using JNDI lookup:

@Bean
public DataSource dataSource() throws Exception {
   Context ctx = new InitialContext();
   return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
}

More details you can find in this article. There is example with JndiDatasourceConfig class.

EDIT 2: I ahve persistence.xml in my project, but it is empty:

<persistence version="2.0" 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_2_0.xsd">
    <persistence-unit name="JPA_And_Spring_Test">
    </persistence-unit>
</persistence>

And I didn't specify any persistent unit name in my java configuration.

like image 129
dimas Avatar answered Sep 27 '22 20:09

dimas