Spring Boot+WebSocket+Stomp+JPA:即使使用@Transactional 注解也没有插入数据
Spring Boot+WebSocket+Stomp+JPA: data not inserted even with @Transactional annotation
我有一个包含两个方法的 RestController:
- 方法
beat1
带有 @MessageMapping 注释从客户端接收 websocket 消息(在 STOMP
协议中)
- 方法
beat2
带有@RequestMapping注解接收来自客户端的HTTP请求
这两个方法里面的代码是一样的:
@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);
}
}
memberService
只是一个普通的 Spring 服务,它包含将使用 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;
}
}
至于我的HeartbeatRepository
的代码:
@Repository
public interface HeatbeatRepository extends JpaRepository<Heartbeat, Integer>
{
}
这是我的问题:
当我使用 websocket 发送 STOMP 消息时,控制器中的 beat1
方法确实执行了,但是,没有数据被插入到 DB table.
当我发送http请求时,controller中的beat2
方法被执行,Heartbeat域对象成功插入到DB中table.
我正在实施 websocket,所以我需要的是让 beat1
正常工作,实际上我根本不需要 beat2
。 (beat2
仅供测试)
由于 beat1
和 beat2
方法都在 memberSevice
中调用相同的服务方法,并且 beat2
有效,我认为我对 MemberService
的实现HeartbeatRepository
没问题。
作为每个 beat1
和 beat2
的休眠日志,
baat1
只打印 SQL 获取下一个 id,我没有看到 SQL 用于插入和事务提交:
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
打印完成的插入 SQL 并提交事务,这就是它成功插入到 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
那么我的代码有什么问题导致 beat1
(WebSocket)和 beat2
(HTTP 请求)出现如此不同的结果?
即使在调用 MessageMapping 方法 beat1
时使用了 @Transactional
注释,似乎 Spring 也不会自动给我一个事务,这很奇怪。
最后我自己找到了解决办法。
事实证明,我必须手动配置 PlatformTransactionManager
使用 @Bean
并使用 JpaTransactionManager
作为实现:
@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);
}
}
就是这样,然后一切正常。
但我仍然不明白为什么 Spring Boot 没有使用我的 applicaion.properites
文件自动配置 PlatformTransactionManager。
也许我在 application.properites
中误解了某些东西或遗漏了某些东西,这是我的 applicaion.properties
的样子:
# 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
我有一个包含两个方法的 RestController:
- 方法
beat1
带有 @MessageMapping 注释从客户端接收 websocket 消息(在STOMP
协议中) - 方法
beat2
带有@RequestMapping注解接收来自客户端的HTTP请求
这两个方法里面的代码是一样的:
@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);
}
}
memberService
只是一个普通的 Spring 服务,它包含将使用 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;
}
}
至于我的HeartbeatRepository
的代码:
@Repository
public interface HeatbeatRepository extends JpaRepository<Heartbeat, Integer>
{
}
这是我的问题:
当我使用 websocket 发送 STOMP 消息时,控制器中的
beat1
方法确实执行了,但是,没有数据被插入到 DB table.当我发送http请求时,controller中的
beat2
方法被执行,Heartbeat域对象成功插入到DB中table.
我正在实施 websocket,所以我需要的是让 beat1
正常工作,实际上我根本不需要 beat2
。 (beat2
仅供测试)
由于 beat1
和 beat2
方法都在 memberSevice
中调用相同的服务方法,并且 beat2
有效,我认为我对 MemberService
的实现HeartbeatRepository
没问题。
作为每个 beat1
和 beat2
的休眠日志,
baat1
只打印 SQL 获取下一个 id,我没有看到 SQL 用于插入和事务提交: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
打印完成的插入 SQL 并提交事务,这就是它成功插入到 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
那么我的代码有什么问题导致 beat1
(WebSocket)和 beat2
(HTTP 请求)出现如此不同的结果?
即使在调用 MessageMapping 方法 beat1
时使用了 @Transactional
注释,似乎 Spring 也不会自动给我一个事务,这很奇怪。
最后我自己找到了解决办法。
事实证明,我必须手动配置 PlatformTransactionManager
使用 @Bean
并使用 JpaTransactionManager
作为实现:
@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);
}
}
就是这样,然后一切正常。
但我仍然不明白为什么 Spring Boot 没有使用我的 applicaion.properites
文件自动配置 PlatformTransactionManager。
也许我在 application.properites
中误解了某些东西或遗漏了某些东西,这是我的 applicaion.properties
的样子:
# 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