Search code examples
javaspringhibernatejpaentitymanager

EntityManager persist and merge not working in @Transactional method


I have strange behaviour of @Transactional annotation and EntityManager persist and merge methods. I've annotated method as @Transactional and in it I call EntityManager.persists(entity)...and nothing happens. Entity is not saved to DB, no exception fired, totally nothing. I've read tons of examples, SO questions, and my code seems ok but not working.

Hibernate config:

import org.hibernate.jpa.HibernatePersistenceProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jndi.JndiTemplate;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.Properties;

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = {"com.mycompany"})
@DependsOn({"PropertiesConfig"})
public class HibernateConfig {

    public HibernateConfig() {}

    @Bean
    public DataSource dataSource() throws NamingException {
        return (DataSource) new JndiTemplate().lookup(PropertyService.getInstance().getProperty("jndi.jdbc.AGSQL"));
    }

    @Bean(name = "entityManager")
    @DependsOn("dataSource")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
        System.out.println("*** Init EntityManagerFactory ***");
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setPersistenceProviderClass(HibernatePersistenceProvider.class);
        em.setDataSource(dataSource());
        em.setPackagesToScan(new String[] { "com.mycompany" });
        em.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
        em.setJpaProperties(hibernateProperties());
        return em;
    }

    @Bean(name = "tm")
    public PlatformTransactionManager transactionManager() throws NamingException {
        System.out.println("*** Init TransactionManager ***");
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
        return transactionManager;
    }

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

    private Properties hibernateProperties() {
        return new Properties() {
            {
                setProperty("hibernate.dialect", PropertyService.getInstance().getProperty("hibernate.dialect"));
                setProperty("show_sql", PropertyService.getInstance().getProperty("show_sql"));
                setProperty("hibernate.connection.isolation", String.valueOf(Connection.TRANSACTION_READ_UNCOMMITTED));
            }
        };
    }
}

Entity:

import org.hibernate.annotations.UpdateTimestamp;    
import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "Vehicle", schema = "dbo")
public class VehicleEntity {
    private Long deviceId;
    private LocalDateTime dtEdit;
    private String name;

    @Id
    @Column(name = "device_id")
    public Long getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(Long deviceId) {
        this.deviceId = deviceId;
    }

    @Basic
    @Column(name = "dt_edit")
    @UpdateTimestamp
    public LocalDateTime getDtEdit() {
        return dtEdit;
    }

    public void setDtEdit(LocalDateTime dtEdit) {
        this.dtEdit = dtEdit;
    }

    @Basic
    @Column(name = "name")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

Dao:

import com.mycompany.VehicleEntity;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Repository
public class VehicleDao {

    @PersistenceContext(unitName = "entityManager")
    private EntityManager entityManager;

    protected EntityManager getEntityManager() {
        return entityManager;
    }

    @Transactional(transactionManager = "tm")
    public void persist(VehicleEntity entity) {
        getEntityManager().persist(entity);
    }

    @Transactional("tm")
    public void merge(VehicleEntity entity) {
        getEntityManager().merge(entity);
    }

}

And executing this code do totally nothing:

VehicleEntity ve = new VehicleEntity();
ve.setDeviceId(111L);
ve.setName("111");
vehicleDao.persist(ve);

I tried to check transaction status inside persist method:

((Session)this.getEntityManager().getDelegate()).getTransaction().getStatus() = NOT_ACTIVE

So I can conclude that transaction has not started, or started, but entitymanager did not see it automatically (see below). In one of the questions I saw that before calling persist(entity) entity manager was joined to the transaction and made the same thing:

@Transactional(transactionManager = "tm")
public void persist(T entity) {
    getEntityManager().joinTransaction();
    getEntityManager().persist(entity);
}

Aaaaand...it works. And transaction status become ACTIVE.

So my questions are: - why it did not work without joinTransaction()? I've never seen it in examples... - (possibly after solving 1st question this one will not make sense) persisting and merging actually are in abstract dao class, but I have many entities, many entities dao and many custom methods in them. And calling joinTransaction() in each of them is not good idea. How make it in pretty way?

EDIT:

Checked one more thing - added propagation property:

@Transactional(transactionManager = "tm", propagation = Propagation.MANDATORY)
public void persist(T entity) {
    getEntityManager().persist(entity);
}

And it throws an exception:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

EDIT2:

Spring version 5.1.7.RELEASE 
Hibernate version 5.4.10.Final
javax.persistence:javax.persistence-api:2.2 (dependency from hibernate-core)

Solution

  • Finally found the problem: I was migrating from SessionFactory to EntityManager and leave in config file old settings for SessionFactory. And the issue was that transaction managers has the same method names for SF and EM:

    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager(SessionFactory sessionFactory) {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(sessionFactory);
        return transactionManager;
    }
    
    @Bean(name = "tm")
    public PlatformTransactionManager transactionManager(@Qualifier("customEntityManager") EntityManagerFactory emf) {
        System.out.println("*** Init TransactionManager ***");
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }
    

    This is mistake though bean names are different. Changing SF transaction manager method to sessionTransactionManager(...) resolve the issue.