#PersistenceContext on two different LocalContainerEntityManagerFactoryBean throws TransactionRequiredException - java

I have two LocalContainerEntityManagerFactoryBeans set up, one with #Primary annotation and one without. This is because I have to communicate with two distinct databases, and if I remove the #Primary decoration I get a duplicate bean error. Both of them are constructed in the exact same way, here's one for reference:
package com.myorg.rest.config.dba;
import ...
#Configuration
#EnableJpaRepositories(
basePackages = "com.myorg.rest.dao.dbA",
entityManagerFactoryRef = "dbAEntityManager",
transactionManagerRef = "dbATransactionManager"
)
#EnableTransactionManagement
public class DbADataSourceConfig {
#Autowired
private EnvProperties settings;
#Bean
#Primary
public DataSource prjDataSource() {
DataSourceProperties ds = settings.getdbADatasource();
return ds.initializeDataSourceBuilder().type(BasicDataSource.class).build();
}
#Bean(name = "dbAEntityManager")
#Primary
public LocalContainerEntityManagerFactoryBean dbAEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(prjDataSource());
em.setPackagesToScan(new String[] { "com.myorg.model.entities.dba" });
em.setPersistenceUnitName("dbAUnit");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
// em.afterPropertiesSet();
return em;
}
#Bean(name = "dbATransactionManager")
#Primary
public PlatformTransactionManager dbATransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(dbAEntityManager().getObject());
return transactionManager;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
}
Both of them are injected inside DAOs with #PersistenceContext(unitName = "dbAUnit") and #PersistenceContext(unitName = "dbBUnit"), and they are both successfully bootstrapping the corresponding EntityManager inside. I can create in both of them and then retrieve the created entity. But when I go to retrieve all entities with the following:
public List<T> findAll() {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<T> entityQuery = criteriaBuilder.createQuery(clazz);
entityQuery.from(clazz);
return entityManager.createQuery(entityQuery).getResultList();
}
One of them works flawlessly and the other one doesn't (it returns 0 entities). After debugging, I've realized that one of them was issuing the insert into... sql command, while the other one wasn't. I tried to force with entityManager.flush() and got
Caused by:
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:413)
at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3398)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1355)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1350)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:366)
at com.sun.proxy.$Proxy95.flush(Unknown Source)
I then tried to decorate the DAOs with #Transactional, and further decorate each individual method with #Transactional. Also tried injecting with #PersistenceContext(unitName = "dbAUnit", type = PersistenceContextType.TRANSACTION), but all of those attempts result in the exact same error.
Why is the 'secondary' #PersistenceContext not putting the transactions in?

When using multiple transaction managers with #Transactional, you need to declare the transaction manager to use explicitly, otherwise the #Primary manager always gets selected, and your secondary EntityManager cannot possibly join its transaction.
Try using #Transactional("dbBTransactionManager") wherever the second persistence context is injected, to see if the problem goes away.

Related

Is there a way to save an #Entity with pre-defined #EmbeddedId value using Spring Data's CrudRepository#save() method?

I'm creating a new service with the goal of consuming Kafka events in an idempotent manner and storing the data into a new PostgreSQL database.
The event will provide data which will be used in the composite key:
#Embeddable
public class MyCompositeKey implements Serializable {
#Column(name="field1", nullable = false)
private UUID field1;
#Column(name="field2", nullable = false)
private UUID field2;
#Column(name="field3", nullable = false)
private UUID field3;
... boilerplate Constructors/getters ...
And the Entity will be referencing it via #EmbeddedId:
#Entity
#Table
public class MyEntity implements Serializable {
#EmbeddedId private MyCompositeKey myCompositeKey;
... Columns/Constructors/getters ...
When an event is consumed, I want to let spring-data-jpa be smart enough to know whether we are replacing data from an existing MyEntity, or creating a new row.
The logic was deemed safe enough to use the CrudRepository#save method before researching the expectation of the logic within that method:
#Transactional
public <S extends T> S save(S entity) {
if (this.entityInformation.isNew(entity)) {
this.em.persist(entity);
return entity;
} else {
return this.em.merge(entity);
}
}
I've gotten to the point where the transactions appear to be completed, but no records are persisted to the table.
I've confirmed via debugging that the call to #save is branching into the return this.em.merge(entity) logic referenced above.
I've only found one possibly helpful blog post[1] for a similar scenario, and am lost on where to go next after it didn't seem to resolve the issue.
The only other option I can foresee is to manually go through a potential three-query execution:
findById
if exists, delete
save
Components
spring-boot-starter 2.0.6
spring-boot-starter-data-jpa 2.0.6
hibernate 5.2.x
References
[1] https://jivimberg.io/blog/2018/11/05/using-uuid-on-spring-data-jpa-entities/
Alright, I found the issue. All of this design was working fine, it was the configuration which was missing.
For some context - Spring Boot seems to configure default javax.sql.DataSource, default javax.persistence.EntityManagerFactory, and default org.springframework.transaction.PlatformTransactionManager beans.
My context was configured with a javax.sql.DataSource bean in order to specify a configuration prefix distinction using org.springframework.boot.context.properties.ConfigurationProperties.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
}
My context did not add in replacements for the dependent javax.persistence.EntityManagerFactory and org.springframework.transaction.PlatformTransactionManager beans.
The fix was to add in all of the configuration. From the docs:
You must create LocalContainerEntityManagerFactoryBean and not EntityManagerFactory directly, since the former also participates in exception translation mechanisms in addition to creating EntityManagerFactory.
The resulting configuration:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.myservice"})
public class RepositoryConfiguration {
#Bean
#ConfigurationProperties(prefix = "myservice.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.myservice");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}

#Sql Failed SQL scripts: The configured DataSource [*] (named 'fooDS') is not the one associated with transaction manager [*] (named 'fooTM')

Update 1 (scroll down)
The setup is as follows:
Our application database is constructed and used by two separate users:
SCHEMA - User that has authority to create and grant permissions on tables and
APP - User who is granted permissions (INSERT, UPDATE, DELETE, SELECT) (by SCHEMA) for above tables to be used.
This enables us to lock any schema changes until needed so no profound changes happen through the app user.
I am running integration tests with a live Oracle database that contains both these users. on the class itself, I use the #SqlConfig(dataSource = "schemaDataSource", transactionManager = "transactionManagerSchema").
On the test method I place two #Sql that fail because in the SqlScriptsTestExecutionListener class, the transaction is not managing the same datasource. (hence the error message further below).
I've tried setting the datasource to the transaction manager manually as shown in my config class below, however some unknown process seems to override it every time. (My best guess is through the #DataJpaTest annotation but I don't know exactly which of the 11 Auto Configurations does it, as you can see I've already disabled a couple with no effect).
Test Class:
#RunWith(SpringRunner.class)
#DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class})
#FlywayTest
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = "transactionManagerSchema")
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class, TestFlywayConfig.class})
#EntityScan(basePackageClasses = BaseEnum.class)
public class NotificationTypeEnumTest {
#Autowired
private EntityManager em;
#Test
#Sql(statements = {"INSERT INTO MYAPP_ENUM (ENUM_ID, \"TYPE\", \"VALUE\") VALUES (MYAPP_ENUM_ID_SEQ.nextval, '" + NotificationTypeEnum.DTYPE + "', 'foo')"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(statements = {"DELETE FROM MYAPP_ENUM"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void canFetchNotificationTypeEnum() throws Exception {
TypedQuery<NotificationTypeEnum> query = em.createQuery("select a from NotificationTypeEnum a", NotificationTypeEnum.class);
NotificationTypeEnum result = query.getSingleResult();
assertEquals("foo", result.getValue());
assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
}
}
DataSource and TM config:
#Slf4j #Configuration #EnableTransactionManagement
public class TestDataSourceConfig {
public static final String SCHEMA_DATA_SOURCE = "schemaDataSource";
public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTransactionManager";
/*Main Datasource and supporting beans*/
#Bean #Primary #ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() { return new DriverManagerDataSource(); }
#Bean #Primary #Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
#Bean(name = SCHEMA_DATA_SOURCE) #ConfigurationProperties(prefix = "myapp.datasource.test_schema")
public DataSource schemaDataSource() { return new DriverManagerDataSource(); }
#Bean(name = SCHEMA_TRANSACTION_MANAGER) #Autowired
public PlatformTransactionManager transactionManagerSchema(#Qualifier(SCHEMA_DATA_SOURCE) DataSource dataSource) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setDataSource(dataSource);
return jpaTransactionManager;
}
}
The full error that I couldn't fit in the title is:
java.lang.IllegalStateException: Failed to execute SQL scripts for test context
...
SOME LONG STACK TRACE
...
the configured DataSource [org.springframework.jdbc.datasource.DriverManagerDataSource] (named 'schemaDataSource') is not the one associated with transaction manager [org.springframework.orm.jpa.JpaTransactionManager] (named 'transactionManagerSchema').
When there is a single DataSource, it appears the Spring auto-configuration model works fine, however, as soon as there are 2 or more, the assumptions break down and the programmer needs to manually fill in the sudden (plentiful) gaps in configuration required.
Am I missing some fundamental understanding surrounding DataSources and TransactionManagers?
Update 1
After some debugging, I have discovered the afterPropertiesSet() method is being called on the bean I created when the TransactionManager is retrieved for use with the #Sql script annotation. This causes whatever EntityManagerFactory it owns (i.e. JpaTransactionManager.entityManagerFactory) to set the datasource according to its configured EntityManagerFactoryInfo.getDataSource(). The EntityManagerFactory itself is being set as a result of the JpaTransactionManager.setBeanFactory method being called (as it implements BeanFactoryAware).
here is the spring code:
// JpaTransactionManager.java
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (getEntityManagerFactory() == null) {
if (!(beanFactory instanceof ListableBeanFactory)) {
throw new IllegalStateException("Cannot retrieve EntityManagerFactory by persistence unit name " +
"in a non-listable BeanFactory: " + beanFactory);
}
ListableBeanFactory lbf = (ListableBeanFactory) beanFactory;
setEntityManagerFactory(EntityManagerFactoryUtils.findEntityManagerFactory(lbf, getPersistenceUnitName()));
}
}
I then tried creating my own EntityManagerFactory bean to attempt to inject it into my created transaction manager but this seems to be opening up Hibernate Specific classes and I wish to stay abstracted at the JPA level. As well as it being difficult to configure at first glance.
Finally, a JPA only solution!
The Solution was to control the creation of the EntityManagerFactoryBeans using the provided spring EntityManagerFactoryBuilder component and inject the EntityManager into the test using the #PersistenceContext annotation.
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED)
...
public class MyJUnitTest {
#PersistenceContext(unitName = "pu")
private EntityManager em;
...
#Test
#Sql(statements = {"SOME SQL USING THE PRIVILEGED SCHEMA CONNECTION"}, ...)
public void myTest() {
em.createQuery("...").getResultList() // uses the APP database user.
}
}
Below is the configuration for both datasources. The application related DataSource beans all have #Primary in their definition to disambiguate any #Autowired dependencies. there are no Hibernate specific classes needed other than the Automatic hibernate config done through the #DataJpaTest class.
#Configuration
#EnableTransactionManagement
#EnableConfigurationProperties(JpaProperties.class)
public class TestDataSourceConfig {
public static final String SCHEMA_DATA_SOURCE = "schemaDS";
public static final String SCHEMA_TRANSACTION_MANAGER = "schemaTM";
public static final String SCHEMA_EMF = "schemaEMF";
/*Main Datasource and supporting beans*/
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return new DriverManagerDataSource();
}
#Bean #Primary #Autowired
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) { return new JpaTransactionManager(emf); }
#Bean #Primary
public LocalContainerEntityManagerFactoryBean emfBean(
EntityManagerFactoryBuilder entityManagerFactoryBuilder,
DataSource datasource,
JpaProperties jpaProperties) {
return entityManagerFactoryBuilder
.dataSource(datasource)
.jta(false)
.packages(CourseOffering.class)
.persistenceUnit("pu")
.properties(jpaProperties.getProperties())
.build();
}
#Bean(name = SCHEMA_EMF)
public LocalContainerEntityManagerFactoryBean emfSchemaBean(
EntityManagerFactoryBuilder entityManagerFactoryBuilder,
#Qualifier(SCHEMA_DATA_SOURCE) DataSource schemaDataSource,
JpaProperties jpaProperties) {
return entityManagerFactoryBuilder
.dataSource(schemaDataSource)
.jta(false)
.packages(CourseOffering.class)
.persistenceUnit("spu")
.properties(jpaProperties.getProperties())
.build();
}
#Bean(name = SCHEMA_DATA_SOURCE)
#ConfigurationProperties(prefix = "myapp.datasource.test_schema")
public DataSource schemaDataSource() { return new DriverManagerDataSource(); }
#Bean(name = SCHEMA_TRANSACTION_MANAGER)
public PlatformTransactionManager transactionManagerSchema(
#Qualifier(SCHEMA_EMF) EntityManagerFactory emfSchemaBean) {
JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
jpaTransactionManager.setEntityManagerFactory(emfSchemaBean);
return jpaTransactionManager;
}
}
Actual Test Class:
#RunWith(SpringRunner.class) // required for all spring tests
#DataJpaTest(excludeAutoConfiguration = {TestDatabaseAutoConfiguration.class, DataSourceAutoConfiguration.class}) // this stops the default data source and database being configured.
#SqlConfig(dataSource = TestDataSourceConfig.SCHEMA_DATA_SOURCE, transactionManager = SCHEMA_TRANSACTION_MANAGER, transactionMode = SqlConfig.TransactionMode.ISOLATED) // make sure the #Sql statements are run using the SCHEMA datasource and txManager in an isolated way so as not to cause problems when running test methods requiring these statements to be run.
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = {TestDataSourceConfig.class})
#TestExecutionListeners({
SqlScriptsTestExecutionListener.class, // enables the #Sql script annotations to work.
SpringBootDependencyInjectionTestExecutionListener.class, // injects spring components into the test (i.e. the EntityManager)
TransactionalTestExecutionListener.class}) // I have this here even though the #Transactional annotations don't exist yet as I plan on using them in further tests.
public class NotificationTypeEnumTest {
#PersistenceContext(unitName = "pu") // required to inject the correct EntityManager
private EntityManager em;
// these statements are
#Test
#Sql(statements = {"INSERT INTO MYAPP_ENUM (ENUM_ID, \"TYPE\", \"VALUE\") VALUES (MYAPP_ENUM_ID_SEQ.nextval, '" + NotificationTypeEnum.DTYPE + "', 'foo')"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
#Sql(statements = {"DELETE FROM MYAPP_ENUM"}, executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void canFetchNotificationTypeEnum() throws Exception {
TypedQuery<NotificationTypeEnum> query = em.createQuery("select a from NotificationTypeEnum a", NotificationTypeEnum.class); // notification type is just a subclass of the BaseEnum type
NotificationTypeEnum result = query.getSingleResult();
assertEquals("foo", result.getValue());
assertEquals(NotificationTypeEnum.DTYPE, result.getConfigType());
}
}
noteworthy classes:
EntityManagerFactoryBuilder - I don't like factory factories, but this one served me well in creating the correct implementation of EntityManagerFactory without depending on any hibernate specific classes. may be injected with #Autowired. The builder bean itself is configured through the HibernateJpaAutoConfiguration class (extends JpaBaseConfiguration) (imported by #DataJpaTest).
JpaProperties - useful for maintaining application.properties config in the resulting entitymanagerfactories. enabled through the #EnableConfigurationProperties(JpaProperties.class) annotation above this config class.
#PersistenceContext(unitName = "...") - I can inject the correct EntityManager in my test class with this annotation.

Changeable Schema for a Specific Entity with JPA and Spring

I'm using Spring and JPA repositories, with multiple database schemas for different entities, and configuring each of them with the proper values and independent beans:
#Configuration
#EnableJpaRepositories(basePackageClasses = {ClassFromSchemaXRepository.class}, entityManagerFactoryRef = "ClassFromSchemaXEntityManagerFactoryA", transactionManagerRef = "ClassFromSchemaXTransactionManager")
{
#Bean
public DataSource classFromSchemaXDataSource(){
HikariDataSource hds = new HikariDataSource();
hds.setJdbcUrl(env.getProperty("dataSource.jdbcUrl.schema.x"));
hds.setUsername(env.getProperty("dataSource.user"));
hds.setPassword(env.getProperty("dataSource.password"));
//...
}
#Bean
public LocalContainerEntityManagerFactoryBean classFromSchemaXEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(classFromSchemaXDataSource());
em.setPackagesToScan(new String[] { "com.company.core.domain.x" });
//...
}
#Bean
public PlatformTransactionManager classFromSchemaXTransactionManager() {
//...
}
}
This configuration works as expected, and the entire set of entities which are located under the 'com.company.core.domain.x' package is mapped to this sets of beans with the schema connection string which is defined in env.getProperty("dataSource.jdbcUrl.schema.x")
However, I am now trying to configure a specific entity to be used by changeable schema and therefor dymanic DataSource/EntityManagerFactory/TransactionManager.
The business logic should determine which schema should be used at runtime.
What is the best method of doing that?
I've found what I was looking for which is extending the AbstractRoutingDataSource class, and overriding the determineCurrentLookupKey() method

Spring-jpa-data: more than one DataSource and one single Spring config

I am trying to use Crud Repository from Spring-jpa-data:
My config with data access beans looks like:
#Configuration
#EnableTransactionManagement
#ComponentScan (basePackages = {"com.comp.olme"})
#PropertySource("classpath:OlmeSmb-${env}.properties")
#EnableJpaRepositories(basePackages="com.comp.olme", entityManagerFactoryRef ="emGapSort", transactionManagerRef = "txManagerGapSort")
#EnableScheduling
public class OlmeSmbConfig {
#Bean
public BasicDataSource olmeDataSource() {
BasicDataSource olmeDataSource = new BasicDataSource();
...
return olmeDataSource;
}
#Bean
public BasicDataSource gapSortDataSource() {
BasicDataSource gapSortDataSource = new BasicDataSource();
...
return gapSortDataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean emFactoryOLME(#Qualifier("olmeDataSource") BasicDataSource olmeDataSource) {
LocalContainerEntityManagerFactoryBean localConnectionFactoryBean = new LocalContainerEntityManagerFactoryBean();
...
return localConnectionFactoryBean;
}
#Bean
public LocalContainerEntityManagerFactoryBean emFactoryGapSort(#Qualifier("gapSortDataSource") BasicDataSource gapSortDataSource) {
LocalContainerEntityManagerFactoryBean localConnectionFactoryBeanGapSort = new LocalContainerEntityManagerFactoryBean();
...
return localConnectionFactoryBeanGapSort;
}
#Bean
public EntityManager emOLME(#Qualifier("emFactoryOLME") EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public EntityManager emGapSort(#Qualifier("emFactoryGapSort") EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
#Bean
public JpaTransactionManager txManagerOLME(#Qualifier("emFactoryOLME") EntityManagerFactory entityManagerFactoryOLME) {
JpaTransactionManager txManagerOLME = new JpaTransactionManager();
...
return txManagerOLME;
}
#Bean
public JpaTransactionManager txManagerGapSort(#Qualifier("emFactoryGapSort") EntityManagerFactory entityManagerFactoryGapSort) {
JpaTransactionManager txManagerGapSort = new JpaTransactionManager();
....
return txManagerGapSort;
}
}
So, as you can see, i have two datasources, two EntityManagerFactories, two TransactionManagers and others...
But i pass only one EntityManagerFactory into #EnableJpaRepositories annotation (entityManagerFactoryRef ="emGapSort").
The question is: how two use more than one DataSources (entityManagerFactory) with Spring-jpa-data?
I read one example where splitting config described as a solution, but i would like to use one single Spring Config. Is it possibe?
Thank you.
From spring boot - #5401
After a careful consideration we've decided not to implement this. That annotation proposal of yours is a mix of configuration key prefix, bean prefix and other bean-related settings. While it sounds appealing on paper, it would be quite hard to implement and probably even harder to keep it consistent with user's customizations.
So, you may try put it on two #Configuration classes (one #EnableJpaRepositories per #Configuration).

Why didn't #Transactional work with #Autowired EntityManagerFactory?

I had a problem with persisting objects to the database using Spring 4.3, JPA 2.1 and Hibernate 5.
Figured out something was wrong with transactions.
Here is my configuration:
#Configuration
#EnableTransactionManagement
public class PersistenceConfig {
/**
* most bean methods skipped, left only the relevant ones
**/
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter){
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactoryBean.setPackagesToScan("com.company");
entityManagerFactoryBean.setJpaProperties(jpaProperties());
return entityManagerFactoryBean;
}
#Bean
#Autowired
JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
Here is my service. The code has run, no exceptions were thrown. But the object was not persisted to the database. I have intuitively understood that either something was wrong with a transaction creation (as the logger didn't show any transactions) or data was not committed to the database. EntityManagerFactory was not null.
#Service
public class Manager {
#Autowired
private EntityManagerFactory entityManagerFactory;
#Transactional
public void persist(Entity entity){
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.persist(entity);
}
}
After I replaced #Autowired EntityManagerFactory with #javax.persistence.PersistenceContext EntityManager, everything worked fine.
#Service
public class Manager {
#javax.persistence.PersistenceContext
private EntityManager entityManager;
#Transactional
public void persist(Entity entity){
entityManager.persist(entity);
}
}
Why doesn't it work with #Autowired EntityManagerFactory?
You are using Spring for transaction management and as such you want to get the current transactional EntityManager. If you are injecting the EntityManagerFactory and use it to get an EntityManager you have a good change you end up with a new one, this new one isn't bound to the started transaction.
Instead inject the EntityManager using #PersistenceContext
#PersistenceContext
private EntityManager em.
If you really want to inject the EntityMangerFactory you have to use #PersistenceUnit instead of #Autowired. The #PersistenceUnit is handled different then a plain #Autowired.
#PersistenceUnit
private EntityManagerFactory emf;

Categories