1

I try to switch to Spring Boot (v2.0.1.RELEASE) and EntityManager. I have spent a week to work on the MySQL database rollback on exception, but still unable to figure it out.

@Repository
public class HibernateDaoImp implements Dao {
    @PersistenceContext 
    private EntityManager entityManager;

    public <T extends AbstractEntity> T saveOrUpdate(T entity) {
        if(entity.getId() == null || entity.getId().equals(0)) {
            this.entityManager.persist(entity);
            t = entity;
        } else {
            t = (T) this.entityManager.merge(entity);
        }
        return t;
    } 
}

@Service("userService")
public class UserServiceImp implements UserService {
    @Autowired
    protected Dao dao;

    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = {ServiceException.class})
    public User saveUser(User user) throws ServiceException {
        user = this.dao.saveOrUpdate(user);
        throw new ServiceException(500, "internal error");
    }
}

The user is still saved in the DB. Here is the loggin:

2018-05-25 10:36:46.297 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.orm.jpa.EntityManagerHolder@4c6ea9e2] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3dd8ed71] to thread [http-nio-8080-exec-4] 2018-05-25 10:36:46.356 DEBUG 25041 --- [nio-8080-exec-4] c.s.knected.controller.UserController : save id=null 2018-05-25 10:36:46.357 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@4c6ea9e2] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3dd8ed71] bound to thread [http-nio-8080-exec-4] 2018-05-25 10:36:46.358 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Bound value [org.springframework.jdbc.datasource.ConnectionHolder@5b4dc571] for key [HikariDataSource (HikariPool-1)] to thread [http-nio-8080-exec-4] 2018-05-25 10:36:46.358 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Initializing transaction synchronization 2018-05-25 10:36:46.358 TRACE 25041 --- [nio-8080-exec-4] o.s.t.i.TransactionInterceptor : Getting transaction for [c.s.k.service.UserServiceImp.saveUser] 2018-05-25 10:36:46.358 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Retrieved value [org.springframework.orm.jpa.EntityManagerHolder@4c6ea9e2] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3dd8ed71] bound to thread [http-nio-8080-exec-4] Hibernate: insert into user (created_by, time_created, deleted, deleted_by, time_deleted, name, time_updated, updated_by, adress, code, email, password, first_name, lost_login, last_name, mobile, phone, photo_id, time_registered, user_type) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'a') 2018-05-25 10:36:46.457 TRACE 25041 --- [nio-8080-exec-4] o.s.t.i.TransactionInterceptor : Completing transaction for [c.s.k.service.UserServiceImp.saveInvite] after exception: c.s.k.service.ServiceException 2018-05-25 10:36:46.457 TRACE 25041 --- [nio-8080-exec-4] o.s.t.i.RuleBasedTransactionAttribute : Applying rules to determine whether transaction should rollback on c.s.k.service.ServiceException 2018-05-25 10:36:46.457 TRACE 25041 --- [nio-8080-exec-4] o.s.t.i.RuleBasedTransactionAttribute : Winning rollback rule is: RollbackRuleAttribute with pattern [c.s.k.service.ServiceException] 2018-05-25 10:36:46.460 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Clearing transaction synchronization 2018-05-25 10:36:46.460 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.jdbc.datasource.ConnectionHolder@5b4dc571] for key [HikariDataSource (HikariPool-1)] from thread [http-nio-8080-exec-4] 2018-05-25 10:36:46.468 TRACE 25041 --- [nio-8080-exec-4] .s.t.s.TransactionSynchronizationManager : Removed value [org.springframework.orm.jpa.EntityManagerHolder@4c6ea9e2] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean@3dd8ed71] from thread [http-nio-8080-exec-4]

I also noticed that if I change the @Transactional for Required to Mandatory:

@Transactional(propagation = Propagation.MANDATORY, rollbackFor = {ServiceException.class})

I got the following error:

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

The following is my pom:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-mail</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-tomcat</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Any help will be appreciated!

-ZJ

Here is my datasource settings in application.properties:

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/mytestdb?useSSL=false
spring.datasource.username=abc
spring.datasource.password=abc123

spring.datasource.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.idle-timeout=1000
spring.datasource.hikari.pool-name=knected-pool

#spring.datasource.tomcat.max-wait=20000
#spring.datasource.tomcat.max-active=50
#spring.datasource.tomcat.max-idle=20
#spring.datasource.tomcat.min-idle=15

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.id.new_generator_mappings = false
zjda
  • 31
  • 2
  • 6
  • from where you are calling saveUser()? I meant within the same class? if yes, this won't work.Please refer https://stackoverflow.com/questions/3423972/spring-transaction-method-call-by-the-method-within-the-same-class-does-not-wo – Sundararaj Govindasamy May 25 '18 at 18:27
  • @SundararajGovindasamy the log clearly shows that's not the case, since the `TransactionInterceptor` is being invoked. – Kayaman May 25 '18 at 18:49
  • Thanks for looking into the issue. saveUser() is called from UserController class with @Autowired UserService. – zjda May 25 '18 at 19:07
  • I also tried to move @Transactional and ServiceException to HibernateDaoImp class, but it did not make any difference. – zjda May 25 '18 at 19:13
  • Based on the log everything seems to work properly. It's almost like the underlying connection is ignoring the rollback and just committing the transaction, or there's not an underlying transaction at all and everything is getting auto-committed. Perhaps you could include the configuration for HikariCP in the question? – Kayaman May 25 '18 at 19:17
  • Track down to roolback() method of org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor class, which calls getConnectionForTransactionManagement().rollback(); I tried 2 connection pools: tomcat and hikariCP. None of them worked. The datasource settings has been added to my original question. Thanks, – zjda May 26 '18 at 04:08

1 Answers1

2

After narrowing down to the underlying connection based on @Kayaman's suggestion, I found that Springboot 2.0 @Transaction doesn't get supported by org.hibernate.dialect.MySQL5Dialect. I changed to MySQL5InnoDBDialect and recreated tables, and the rollback works as expected!

I should post my datasource configuration in the first place.

Many thanks to @Kayaman and @Sundararaj Govindasamy for your helps!

Karol Dowbecki
  • 43,645
  • 9
  • 78
  • 111
zjda
  • 31
  • 2
  • 6