I am studying Spring JPA integration and come up with strange behavior when trying to autowire EntityManager with #Autowired annotation.
I have a Spring test that has no Spring Boot context support:
#ExtendWith(SpringExtension.class)
class JpaTransactionScopedPersistentContextTests {
#Configuration
#ComponentScan(basePackages = {
"packages"
})
static class TestConfiguration {}
#Autowired
private EntityAService entityAService;
#Test
void testEntityManagerWithoutTransaction () {
EntityA entityA = new EntityA();
entityAService.save(entityA);
}
}
The service is defined as follows:
#Service
public class EntityAService {
// #PersistenceContext - this works
#Autowired // this fails
private EntityManager entityManager;
public EntityA save (EntityA entityA) {
return entityManager.merge(entityA);
}
#Transactional
public EntityA saveInTransaction (EntityA entityA) {
return entityManager.merge(entityA);
}
}
I have the following JPA configuration:
#EnableTransactionManagement
#Configuration
public class ConfigurationA {
#Bean
public DataSource dataSourceWithEmbeddedDatabaseBuilder () throws SQLException {
return new EmbeddedDatabaseBuilder()
.setName("test")
.setType(EmbeddedDatabaseType.H2)
.addScripts("classpath:/schema.sql")
.addScripts("classpath:/data.sql")
.build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory (DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean =
new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setPackagesToScan("packages");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
return entityManagerFactoryBean;
}
#Bean
public PlatformTransactionManager jpaTransactionManager (EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Spring throws an error:
No qualifying bean of type 'javax.persistence.EntityManager' available: expected at least 1 bean which qualifies as autowire candidate.
Why?
After hours of debugging a bunch of internal Spring code I have found the reason.
This:
#Autowired
private EntityManager entityManager;
Works only when you have #EnableJpaRepositories annotation.
For example:
#EnableJpaRepositories
#Configuration
public class ConfigurationA {}
That is, autowiring of EntityManager is a feature that is provided by Spring Data JPA module. It is not provided by usual Spring JPA integration.
Related
I have been stuck trying to get multiple datasources hooked up with spring boot with a couple custom query implementations. When I call those customized queries the correct entity manager is used however when I use the basic crudrepository functions, it uses the primary manager and produces "Not a managed type" error.
Here are my two sources config:
#Configuration
public class DataSourceConfig {
#Bean(name="DB1")
#Primary
#ConfigurationProperties(prefix="spring.datasource")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#PersistenceContext(unitName = "primary")
#Qualifier("entityManagerFactory")
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(primaryDataSource())
.persistenceUnit("primary")
.packages("com.company.monitoring.db.primary")
.build();
}
and
#Configuration
public class AuditLogDataSourceConfig {
#PersistenceContext(unitName = "auditlog")
#Bean(name="DB2")
#ConfigurationProperties(prefix="auditlog.datasource")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
#PersistenceContext(unitName = "secondary")
#Qualifier("secondaryEntityManager")
#Bean(name = "secondaryEntityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder.dataSource(secondaryDataSource())
.persistenceUnit("secondary")
.packages("com.company.monitoring.db.secondary" )
.build();
}
}
Repo in controller that uses the wrong datasource:
import com.company.monitoring.db.secondary.repository.FileAuditLogDAO;
...
#RequestMapping("/eventdetails/")
public class EventDetailsController {
...
#Autowired
private FileAuditLogDAO repository;
...
FileAuditLogDAO is in package com.company.monitoring.db.secondary.repository
and FileAuditLogEntity is in package com.company.monitoring.db.secondary.entity.
I know the primary is being used since when I add "com.company.monitoring.db.secondary" to the primarys packages I no longer get the Not a managed type: class com.company.monitoring.db.secondary.entity.FileAuditLogEntity. I have tried various places for #EnableJPARepositories with no success. What am I doing wrong here?
Maybe it is kind of too common but still.
I have a small test project where I'm testing all the JPA stuff. Almost everywhere I'm using Spring Data and JPA repositories work just fine. But now I'm trying to make my service to save entities. The service looks something like this:
#Service
public class SomeServiceImpl implements SomeService {
#Autowired
private EntityManagerFactory entityManagerFactory;
public SomeServiceImpl(EntityManagerFactory entityManagerFactory) {
this.entityManagerFactory = entityManagerFactory;
}
#Override
#Transactional
public SomeEntity save(SomeEntity someEntity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(someEntity);
return someEntity;
}
The persistence config looks like this (I'm intentionally copying and pasting the whole config. Maybe it would help you to reproduce the error):
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories
#PropertySource({"classpath:conf/application.properties"})
public class PersistenceConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() throws SQLException {
PoolDataSourceImpl dataSource = new PoolDataSourceImpl();
dataSource.setConnectionFactoryClassName(environment.getRequiredProperty("db.driverClassName"));
dataSource.setURL(environment.getRequiredProperty("db.url"));
dataSource.setUser(environment.getRequiredProperty("db.username"));
dataSource.setPassword(environment.getRequiredProperty("db.password"));
dataSource.setFastConnectionFailoverEnabled(
Boolean.valueOf(environment.getRequiredProperty("db.fast.connect.failover.enabled")));
dataSource.setValidateConnectionOnBorrow(true);
dataSource.setSQLForValidateConnection("SELECT SYSDATE FROM DUAL");
dataSource.setONSConfiguration(environment.getRequiredProperty("db.ons.config"));
dataSource.setInitialPoolSize(Integer.valueOf(environment.getRequiredProperty("db.initial.pool.size")));
dataSource.setMinPoolSize(Integer.valueOf(environment.getRequiredProperty("db.min.pool.size")));
dataSource.setMaxPoolSize(Integer.valueOf(environment.getRequiredProperty("db.max.pool.size")));
dataSource.setAbandonedConnectionTimeout(0);
dataSource.setInactiveConnectionTimeout(60 * 25);
dataSource.setTimeToLiveConnectionTimeout(0);
dataSource.setMaxConnectionReuseTime(60 * 30L);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.setProperty("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.setProperty("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
properties.setProperty("hibernate.hbm2ddl.auto", environment.getRequiredProperty("hibernate.hbm2ddl.auto"));
return properties;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Autowired DataSource dataSource) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setPackagesToScan("com.dropbinc.learning.jpa.model");
JpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
Map jpaProperties = new HashMap();
jpaProperties.put("javax.persistence.schema-generation.database.action", "drop-and-create");
entityManagerFactory.setJpaPropertyMap(jpaProperties);
entityManagerFactory.setJpaProperties(hibernateProperties());
return entityManagerFactory;
}
#Bean
public PlatformTransactionManager transactionManager(#Autowired EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
}
And one more where I'm planning to configurate the rest of the application (placed in the same package):
#Configuration
#ComponentScan(basePackages = "com.dropbinc.learning.jpa")
public class AppConfig {
}
I've tried to debug Spring but all that I wasn't able to detect a difference between transaction behaviour of JPA repositories and my service. I saw transaction was created and even commited. But in case of JPA repositories it got saved while in my service implementation it did generated ids but an entity didn't appeared in a database.
I'm running all the stuff in tests, autowiring the service by interface:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { AppConfig.class, PersistenceConfig.class })
public class AllTheTests {
#Autowired
SomeService someService;
...
}
Thank you very much for any suggestion!
EDIT Adding entityManager.flush() call generates nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress.
I am new to the programming world so what I say may seem silly.
I am trying to run a spring-boot test as JUnit under Eclipse but I just can't figure out how to use the spring-boot annotations... I have read several guides and browsed this website but didn't find anything that resolved my problem.
I am trying to run the JUnit test-class below :
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={CBusiness.class,CService.class,CDao.class}, loader = AnnotationConfigContextLoader.class)
#SpringBootTest
public class CalculTest {
#Autowired
CBusiness business;
#Test
public void testCalcul() throws TechnicalException {
Object object= new Object();
object.setId1("00");
object.setId2("01");
object.setNombrePlacesMaximum(new BigInteger("50"));
Long result=business.calcul(object);
assertTrue(result>0);
}
Running this as a JUnit test gives me the following exception :
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cDao': Injection of persistence dependencies failed; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManagerFactory' available
The EntityManager parameter from the CDao class has the annotation #PersistenceContext, I thought this meant it was automatically generated by Hibernate but apparently it isn't... How can I instanciate the EntityManager using only java code? I don't have any .xml or .properties file...
FYI here are the classes called by the test :
Business Layer :
#Component("cBusiness")
public class CBusiness {
#Autowired
CService cService;
public long calcul(Object object) throws TechnicalException {
//Code (calls a method from CService class)
}
Service layer :
#Service
public class CService {
#Autowired
CDao cDao;
Dao Layer
#Repository
#Transactional(rollbackFor = {TechnicalException.class})
public class CDao {
#PersistenceContext
EntityManager entityManager;
I tried testing the method inside a webservice using only the #autowire annotation on the Business layer and if worked fine, however I just cannot instanciate it in the JUnit tests. I tried several ways of running this test and I am not sure this is the right way of doing it, so I'm open to any suggestion.
Thanks in advance.
#Configuration
#EnableTransactionManagement
public class PersistenceJPAConfig{
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "\\your package here" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("\\Driver");
dataSource.setUrl("\\URL");
dataSource.setUsername( "\\userName" );
dataSource.setPassword( "\\password" );
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
At the outset, I have tried the options mentioned in various forums for the same stack trace I get. A few of them did not work while with others (like removing javax.persistence.transactiontype) I did not understand how and where to try it.
I am using Spring Boot data JPA (1.2 RC2) + Hibernate (with a custom persistence.xml). Here is my Application.java
#Configuration
#ComponentScan(<our package>)
#EnableAutoConfiguration(exclude = EmbeddedServletContainerAutoConfiguration.class)
#EnableTransactionManagement
#DependsOn("transactionManager")
#EnableJpaRepositories(transactionManagerRef = "transactionManager")
public class Application {
public static void main(String[] args) {
run(Application.class, args);
}
}
My RepositoryConfiguration (as we have custom persistence.xml - currently need to reuse it)
#Configuration
public class RepositotyConfiguration {
#Autowired
private DataSource dataSource;
#Value("${db.dialect}")
private String dialectClass;
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
LocalContainerEntityManagerFactoryBean entityManagerFactory = builder.dataSource(dataSource).
persistenceUnit("main").build();
entityManagerFactory.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
Properties additionalProperties = new Properties();
additionalProperties.put("hibernate.dialect", dialectClass);
entityManagerFactory.setJpaProperties(additionalProperties);
return entityManagerFactory;
}
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
return txManager;
}
}
The transactionManager here is created if I do not have a single Repository but the moment I add one, ny tests fail with this exception:
Caused by: java.lang.NullPointerException
at org.hibernate.engine.transaction.internal.jta.JtaStatusHelper.getStatus(JtaStatusHelper.java:76)
at org.hibernate.engine.transaction.internal.jta.JtaStatusHelper.isActive(JtaStatusHelper.java:118)
at org.hibernate.engine.transaction.internal.jta.CMTTransaction.join(CMTTransaction.java:149)
My test application context is:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#WebAppConfiguration
#EnableAutoConfiguration
#TransactionConfiguration(transactionManager = "transactionManager")
#IntegrationTest("server.port:0")
#ActiveProfiles("test")
public abstract class TestApplicationContext {
#Autowired
private WebApplicationContext wac;
#Value("${local.server.port}")
private int port;
...
}
An example (not the actual) of the repository I try to add (where let's say Item is a model object)
public interface ItemRepository extends CrudReposity<Item, Long> {
Item findByCode(String code); // this seems to cause the problem, assume 'code' is field in Item
}
Any pointers will be of utmost help.
EDIT: It now fails only if I add extra method in ItemRepository say Item findByItemCode(String itemCode) where let's say itemCode is a field in Item model, but can't understand why?
Thanks,
Paddy
I'm getting errors trying to inject resource dependencies into my unit testing.
My approach has been to write a TestConfig.java to replace the applicationContext.xml for production which manages the connections of the beans. So that I can run it with an in-memory database and just test components.
TestConfig.java
#Configuration
#EnableTransactionManagement
public class TestConfig {
#Bean
public DataSource dataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.hsqldb.jdbcDriver");
ds.setUrl("jdbc:hsqldb:mem:testdb");
ds.setUsername("sa");
ds.setPassword("");
return ds;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(){
LocalContainerEntityManagerFactoryBean lcemfb
= new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(this.dataSource());
lcemfb.setPackagesToScan(new String[] {"com.dao","com.data"});
lcemfb.setPersistenceUnitName("MyTestPU");
HibernateJpaVendorAdapter va = new HibernateJpaVendorAdapter();
lcemfb.setJpaVendorAdapter(va);
Properties ps = new Properties();
ps.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
ps.put("hibernate.hbm2ddl.auto", "create");
lcemfb.setJpaProperties(ps);
lcemfb.afterPropertiesSet();
return lcemfb;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(this.entityManagerFactoryBean().getObject());
return tm;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
#Bean
public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor()
{
return new AutowiredAnnotationBeanPostProcessor();
}
}
ProductsDaoTest.java
#ContextConfiguration(classes = { TestConfig.class })
#RunWith(SpringJUnit4ClassRunner.class)
public class ProductsDaoTest {
#Resource(name="com.dao.ProductsDao")
private ProductsDao testDao;
#Test
public void testSaveProduct() {
Product productA = new Product();
testDao.save(productA);
Set<Product> products = testDao.getAllProducts();
assertNotNull(products);
}
}
The error is Error creating bean with name 'com.dao.ProductsDaoTest': Injection of resource dependencies failed
So it can't find the ProductDao Bean which is a #Repository with a #Autowired sessionFactory.
So my guess is that because I'm not naming the beans using xml it can't find it, though I thought it should automatically pick it up from setPackagesToScan(). So is there a way to manually insert the Bean mapping so that it can be found?
Also more generally is this a reasonable way to go about testing Spring DAO configurations?
Regards,
Iain
I think you are trying to use wrong name of your DAO bean in #Resource annotation. Have you explicitly specify name of the ProductsDao bean using #Qualifier? If no, then as I remember by default the name of the bean will be productsDao. So you should inject your DAO like:
#Resource(name="productsDao")
private ProductsDao testDao;
If you have only one ProductDAO implementation then simply write:
#Autowired
private ProductsDao testDao;
or
#Inject
private ProductsDao testDao;
In case if you want to give specific name to DAO then use next construction:
#Respository
#Qualifier(name="specificName")
public class ProductDAO...
EDIT:
As Boris noted you should also specify which package to scan for defined beans (classes annotated with #Component, #Service, #Repository...). For this you should add #ComponentScan annotation to your configuration class definition.
#Configuration
#EnableTransactionManagement
#ComponentScan(basePackages = {"package_to_scan"})
public class TestConfig {...}