junhanlin
junhanlin

Reputation: 53

Spring Boot+WebSocket+Stomp+JPA: data not inserted even with @Transactional annotation

I have a RestController which contains two method:

  1. method beat1 with @MessageMapping annotation to receive the websocket message (in STOMP protocol) from client
  2. method beat2 with @RequestMapping annotation to receive HTTP request from client

The two methods have the same code inside:

@RestController
@RequestMapping("/api/member")
@MessageMapping("member")
public class MemberCtrl
{
  @MessageMapping("beat")
  public Heartbeat beat1(@Payload Heartbeat heartbeat)
  {
    return memberService.beat(heartbeat);
  }

  @RequestMapping(value = "/beat", method = RequestMethod.POST)
  public Heartbeat beat2(@RequestBody Heartbeat heartbeat)
  {
    return memberService.beat(heartbeat);
  }
}

The memberService is just a normal Spring service that contains the business logic which will do a database data insertion with Spring JPARepository:

@Service
public class MemberServiceImpl implements MemberService, UserDetailsService
{

    @Autowired
    private HeartbeatRepository heartbeatRepository;

    @Transactional
    @Override
    public Heartbeat beat(Heartbeat heartbeat)
    {
        heartbeat = heartbeatRepository.save(heartbeat);
        return heartbeat;
    }
}

As for the code of my HeartbeatRepository:

@Repository
public interface HeatbeatRepository extends JpaRepository<Heartbeat, Integer>
{

}

Here's my problem:

  1. When I send a STOMP message with websocket, the beat1 method in the controller do execute, however, no data was inserted into DB table.

  2. When I send a http request, the beat2 method in controller is executed, and the Heartbeat domain object was successfully inserted into DB table.

I'm implementing websocket, so what I need is to get beat1 works, I actually don't need beat2 at all. (beat2 is just for testing)

Since both beat1 and beat2 method are calling the same Service method in memberSevice, and beat2 works, I think my implementation of MemberService and HeartbeatRepository have no problem.

As fot the hibernate's log for each of beat1 and beat2,

  1. baat1 only print the SQL for getting next id, and I didn't see the SQL for insertion and transaction commit:

    2016-08-23 18:32:56.227 DEBUG 43874 --- [nboundChannel-4] org.hibernate.SQL                        : select nextval ('heartbeat_id_seq')
    2016-08-23 18:32:56.227 DEBUG 43874 --- [nboundChannel-4] o.h.e.j.internal.LogicalConnectionImpl   : Obtaining JDBC connection
    2016-08-23 18:32:56.242 DEBUG 43874 --- [nboundChannel-4] o.h.e.j.internal.LogicalConnectionImpl   : Obtained JDBC connection
    2016-08-23 18:32:56.261 DEBUG 43874 --- [nboundChannel-4] org.hibernate.id.SequenceGenerator       : Sequence identifier generated: BasicHolder[java.lang.Integer[20]]
    2016-08-23 18:32:56.261 DEBUG 43874 --- [nboundChannel-4] o.h.e.i.AbstractSaveEventListener        : Generated identifier: 20, using strategy: org.hibernate.id.SequenceHiLoGenerator
    2016-08-23 18:32:56.266 DEBUG 43874 --- [nboundChannel-4] o.h.e.j.internal.LogicalConnectionImpl   : Releasing JDBC connection
    2016-08-23 18:32:56.266 DEBUG 43874 --- [nboundChannel-4] o.h.e.j.internal.LogicalConnectionImpl   : Released JDBC connection
    

2.beat2 do print the completed insetaion SQL and commit the transaction, so that's why it succeed to do the insertion to DB table:

2016-08-23 18:20:29.937 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.spi.AbstractTransactionImpl      : begin
2016-08-23 18:20:29.937 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.j.internal.LogicalConnectionImpl   : Obtaining JDBC connection
2016-08-23 18:20:29.951 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.j.internal.LogicalConnectionImpl   : Obtained JDBC connection
2016-08-23 18:20:29.951 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.internal.jdbc.JdbcTransaction    : initial autocommit status: true
2016-08-23 18:20:29.951 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.internal.jdbc.JdbcTransaction    : disabling autocommit
2016-08-23 18:20:29.966 DEBUG 43846 --- [nio-8989-exec-9] org.hibernate.SQL                        : select nextval ('heartbeat_id_seq')
2016-08-23 18:20:29.986 DEBUG 43846 --- [nio-8989-exec-9] org.hibernate.id.SequenceGenerator       : Sequence identifier generated: BasicHolder[java.lang.Integer[19]]
2016-08-23 18:20:29.986 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.i.AbstractSaveEventListener        : Generated identifier: 19, using strategy: org.hibernate.id.SequenceHiLoGenerator
2016-08-23 18:20:29.992 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.spi.AbstractTransactionImpl      : committing
2016-08-23 18:20:29.993 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.i.AbstractFlushingEventListener    : Processing flush-time cascades
2016-08-23 18:20:29.993 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.i.AbstractFlushingEventListener    : Dirty checking collections
2016-08-23 18:20:29.995 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.i.AbstractFlushingEventListener    : Flushed: 1 insertions, 0 updates, 0 deletions to 1 objects
2016-08-23 18:20:29.995 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.i.AbstractFlushingEventListener    : Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections
2016-08-23 18:20:29.996 DEBUG 43846 --- [nio-8989-exec-9] o.hibernate.internal.util.EntityPrinter  : Listing entities:
2016-08-23 18:20:29.998 DEBUG 43846 --- [nio-8989-exec-9] o.hibernate.internal.util.EntityPrinter  : com.yamk.api.domain.orm.Heartbeat{lifespanSec=0, lostedSec=0, lastHeartbeatTime=Tue Aug 23 18:19:35 UTC 2016, createdTime=Tue Aug 23 18:20:29 UTC 2016, location=POINT (121.56818389892578 25.033194472364688), id=19}
2016-08-23 18:20:30.006 DEBUG 43846 --- [nio-8989-exec-9] org.hibernate.SQL                        : insert into heartbeat (created_time, last_heartbeat_time, lifespan_sec, location, losted_sec, id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into heartbeat (created_time, last_heartbeat_time, lifespan_sec, location, losted_sec, id) values (?, ?, ?, ?, ?, ?)
2016-08-23 18:20:30.088 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.internal.jdbc.JdbcTransaction    : committed JDBC Connection
2016-08-23 18:20:30.088 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.t.internal.jdbc.JdbcTransaction    : re-enabling autocommit
2016-08-23 18:20:30.091 DEBUG 43846 --- [nio-8989-exec-9] m.m.a.RequestResponseBodyMethodProcessor : Written [com.yamk.api.domain.orm.Heartbeat@32] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@69f8302c]
2016-08-23 18:20:30.091 DEBUG 43846 --- [nio-8989-exec-9] o.s.web.servlet.DispatcherServlet        : Null ModelAndView returned to DispatcherServlet with name 'dispatcherServlet': assuming HandlerAdapter completed request handling
2016-08-23 18:20:30.091 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.jdbc.internal.JdbcCoordinatorImpl  : HHH000420: Closing un-released batch
2016-08-23 18:20:30.091 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.j.internal.LogicalConnectionImpl   : Releasing JDBC connection
2016-08-23 18:20:30.091 DEBUG 43846 --- [nio-8989-exec-9] o.h.e.j.internal.LogicalConnectionImpl   : Released JDBC connection

So what's the problem with my code that causing such a different outcome on beat1 (WebSocket) and beat2 (HTTP Request) ?

It seems that Spring does not automatically give me a transaction even if @Transactional annotation is used when MessageMapping method beat1 is called and that is wierd.

Upvotes: 1

Views: 1742

Answers (1)

junhanlin
junhanlin

Reputation: 53

Finally I found the solution by myself. It turns out that I have to manually configurate PlatformTransactionManager using @Bean and using JpaTransactionManager as the implementation:

    @ComponentScan
    @Configuration
    @EnableSwagger2
    @EnableCaching
    @EnableJpaRepositories
    @EnableTransactionManagement
    @EnableJpaAuditing
    @SpringBootApplication
    public class App
    {
      public static void main(String[] args)
      {
          SpringApplication.run(App.class, args);
      }

      @Autowired
      private EntityManagerFactory entityManagerFactory;

      @Bean
      public PlatformTransactionManager transactionManager() 
      {
        return new JpaTransactionManager(entityManagerFactory);
      }

    }

That's all, then everything works.

But I still don't understand why Spring Boot didn't auto-configurate the PlatformTransactionManager using my applicaion.properites file. Maybe I misunderstood something or missed something in application.properites , here's how my applicaion.properties looks like:

    # data source
    spring.datasource.driver-class-name=org.postgresql.Driver
    spring.datasource.url=jdbc:postgresql://xxx.xxx.xxx.xxxx:####/xxxx
    spring.datasource.username=my-user-name
    spring.datasource.password=my-password
    spring.datasource.validationQuery=SELECT 1
    spring.datasource.test-on-borrow=true

    # jpa / hibernate
    spring.jpa.hibernate.ddl-auto:validate
    spring.jpa.show-sql:true
    spring.jpa.properties.hibernate.dialect = org.hibernate.spatial.dialect.postgis.PostgisDialect
    spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
    logging.level.org.hibernate=DEBUG

Upvotes: 3

Related Questions