1
votes

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
1
from where you are calling saveUser()? I meant within the same class? if yes, this won't work.Please refer stackoverflow.com/questions/3423972/… - Sundararaj Govindasamy
@SundararajGovindasamy the log clearly shows that's not the case, since the TransactionInterceptor is being invoked. - Kayaman
Thanks for looking into the issue. saveUser() is called from UserController class with @Autowired UserService. - zjda
I also tried to move @Transactional and ServiceException to HibernateDaoImp class, but it did not make any difference. - zjda
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

1 Answers

2
votes

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!