javajpaspring-data-jpaspring-dataspring-scheduled

Spring job doesn't store JPA entity to DB and doesn't throw any exception even if table is not exist


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


Solution

  • 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;
    }