I have a problem with storing JPA entity in Spring job, when I hit my code via REST my entity is stored to DB, but I need to have some automatization part via scheduler, but it doesn't work, and it doesn't show any exception even if I drop my table (I'm using Oracle DB)
My entity
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.*;
@Entity
@Table(name = "MY_ENTITY")
@Getter
@Setter
public class MyEntity {
@Id
@Column(name = "ident")
@GeneratedValue(generator = "uuid-hibernate-generator")
@GenericGenerator(name = "uuid-hibernate-generator", strategy = "org.hibernate.id.UUIDGenerator")
private String ident;
@Column(name = "content")
private String content;
}
Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
@Transactional
public interface MyEntityRepository extends JpaRepository<MyEntity, String> {
}
Service class
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
@Slf4j
public class TestService {
@Autowired
private MyEntityRepository myEntityRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void storeMyEntity(){
try {
MyEntity myEntity = new MyEntity();
myEntity.setContent("ABC");
myEntityRepository.save(myEntity);
} catch (Exception ex){
log.error("Error", ex);
}
}
}
Job
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class TestJob {
@Autowired
private TestService testService;
@Scheduled(fixedRateString = "10000")
public void testJob() {
log.info("store DB entity in job - started");
testService.storeMyEntity();
log.info("store DB entity in job - finished");
}
}
My data source
@Bean
public DataSource myDataSource() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setConnectionTimeout(connectionTimeoutMillis);
config.setMaximumPoolSize(maximumPoolSize);
return new HikariDataSource(config);
}
@Bean(name = "transactionManager")
public DataSourceTransactionManager transactionManager(DataSource myDataSource) {
return new DataSourceTransactionManager(myDataSource);
}
Whenever is my job triggered - table is not in DB so I would expect error, but nothing happen - I would expect table doesn't exist, but see the logs all is clear
11:38:29.851 [scheduling-1] INFO playground.TestJob - store DB entity in job - started
11:38:29.851 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Creating new transaction with name [playground.TestService.storeMyEntity]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
11:38:29.855 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Acquired Connection [HikariProxyConnection@1783732892 wrapping oracle.jdbc.driver.T4CConnection@16a5eb6d] for JDBC transaction
11:38:29.855 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Switching JDBC Connection [HikariProxyConnection@1783732892 wrapping oracle.jdbc.driver.T4CConnection@16a5eb6d] to manual commit
11:38:29.855 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Participating in existing transaction
11:38:29.855 [scheduling-1] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Opening JPA EntityManager
11:38:29.855 [scheduling-1] DEBUG o.h.e.i.AbstractSaveEventListener - Generated identifier: 0e68b146-0092-4fd1-98e3-2403a915196e, using strategy: org.hibernate.id.UUIDGenerator
11:38:29.855 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Initiating transaction commit
11:38:29.855 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Committing JDBC transaction on Connection [HikariProxyConnection@1783732892 wrapping oracle.jdbc.driver.T4CConnection@16a5eb6d]
11:38:29.858 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Releasing JDBC Connection [HikariProxyConnection@1783732892 wrapping oracle.jdbc.driver.T4CConnection@16a5eb6d] after transaction
11:38:29.858 [scheduling-1] INFO c.c.r.m.reporting.playground.TestJob - store DB entity in job - finished
The second strange thing is if I change my service class from
myEntityRepository.save(myEntity);
to myEntityRepository.saveAndFlush(myEntity);
I get exception that there is no transaction, but in the log I can see that transaction has been created
11:44:18.181 [scheduling-1] INFO c.c.r.m.reporting.playground.TestJob - store DB entity in job - started
11:44:18.181 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Creating new transaction with name [playground.TestService.storeMyEntity]: PROPAGATION_REQUIRES_NEW,ISOLATION_DEFAULT
11:44:18.184 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Acquired Connection [HikariProxyConnection@1365602219 wrapping oracle.jdbc.driver.T4CConnection@53c1179a] for JDBC transaction
11:44:18.185 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Switching JDBC Connection [HikariProxyConnection@1365602219 wrapping oracle.jdbc.driver.T4CConnection@53c1179a] to manual commit
11:44:18.185 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Participating in existing transaction
11:44:18.185 [scheduling-1] DEBUG o.s.o.jpa.EntityManagerFactoryUtils - Opening JPA EntityManager
11:44:18.185 [scheduling-1] DEBUG o.h.e.i.AbstractSaveEventListener - Generated identifier: 2a056d01-636f-48ca-a595-3e164a3110ac, using strategy: org.hibernate.id.UUIDGenerator
11:44:18.185 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Participating transaction failed - marking existing transaction as rollback-only
11:44:18.185 [scheduling-1] DEBUG o.s.j.d.DataSourceTransactionManager - Setting JDBC transaction [HikariProxyConnection@1365602219 wrapping oracle.jdbc.driver.T4CConnection@53c1179a] rollback-only
11:44:18.185 [scheduling-1] ERROR c.c.r.m.r.playground.TestService - Error
org.springframework.dao.InvalidDataAccessApiUsageException: no transaction is in progress; nested exception is javax.persistence.TransactionRequiredException: no transaction is in progress
EDIT:
I also tried to use oracle sequence for ID
@Entity
@Table(name = "MY_ENTITY")
@Getter
@Setter
public class MyEntity {
@Id
@GeneratedValue(generator="seqGen")
@SequenceGenerator(name="seqGen",sequenceName="my_entity_seq", allocationSize=1)
private Long id;
@Column(name = "content")
private String content;
}
In this case when My TestService call save() - I'm able to retrive an ID from oracle but there is still no information that table doesn't exist - if sequence is not created, than application throws exception that seqeunce doesn't exist, but for table - nothing
The problem was in DataSourceTransactionManager - it works with this configuration
@Bean
public DataSource myDataSource() throws SQLException {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setConnectionTimeout(connectionTimeoutMillis);
config.setMaximumPoolSize(maximumPoolSize);
return new HikariDataSource(config);
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.ORACLE);
jpaVendorAdapter.setShowSql(false);
jpaVendorAdapter.setGenerateDdl(false);
jpaVendorAdapter.setDatabasePlatform("ExtendedOracle10gDialect");
return jpaVendorAdapter;
}
@Bean(name = "entityManager")
public EntityManager entityManager() throws SQLException {
return entityManagerFactory().createEntityManager();
}
@Primary
@Bean(name = "entityManagerFactory")
public EntityManagerFactory entityManagerFactory() throws SQLException {
LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
emf.setDataSource(myDataSource());
emf.setJpaVendorAdapter(jpaVendorAdapter());
emf.setPackagesToScan("my.package");
emf.setPersistenceUnitName("default");
emf.afterPropertiesSet();
return emf.getObject();
}
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() throws SQLException {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(entityManagerFactory());
return tm;
}