级联双向@OneToOne与@MapsId

huangapple go评论98阅读模式
英文:

Cascade on bidirectional @OneToOne with @MapsId

问题

我使用的是Spring Boot 2.1.1.RELEASE 和 Hibernate 5.3.7.FINAL 版本。

规则是,用户可以没有电话(user 中的 phone 可为空),但是电话不能没有用户(phone 中的 user 不可为空)。

实体类:

  1. @Entity
  2. public class Phone {
  3. @Id
  4. private Long id;
  5. @OneToOne
  6. @MapsId
  7. @JoinColumn(name = "id")
  8. private User user;
  9. public Long getId() {
  10. return id;
  11. }
  12. public void setId(final Long id) {
  13. this.id = id;
  14. }
  15. public User getUser() {
  16. return user;
  17. }
  18. public void setUser(final User user) {
  19. this.user = user;
  20. }
  21. }
  22. @Entity
  23. public class User {
  24. @Id
  25. @GeneratedValue(strategy = GenerationType.IDENTITY)
  26. private Long id;
  27. @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
  28. private Phone phone;
  29. public Long getId() {
  30. return id;
  31. }
  32. public void setId(final Long id) {
  33. this.id = id;
  34. }
  35. public Phone getPhone() {
  36. return phone;
  37. }
  38. public void setPhone(final Phone phone) {
  39. this.phone = phone;
  40. }
  41. }

控制器:

  1. @RestController
  2. @RequestMapping
  3. public class UserController {
  4. private final UserService userService;
  5. public UserController(final UserService userService) {
  6. this.userService = userService;
  7. }
  8. @GetMapping("/demo")
  9. public void createUserAndAddPhone() {
  10. final User user = new User();
  11. userService.save(user);
  12. final Phone phone = new Phone();
  13. phone.setUser(user);
  14. user.setPhone(phone);
  15. userService.update(user);
  16. }
  17. }

仓库:

  1. public interface UserRepository extends PagingAndSortingRepository<User, Long> {
  2. }

服务:

  1. @Service
  2. public class UserService {
  3. private final UserRepository userRepository;
  4. public UserService(final UserRepository userRepository) {
  5. this.userRepository = userRepository;
  6. }
  7. @Transactional
  8. public void save(final User user) {
  9. userRepository.save(user);
  10. }
  11. @Transactional
  12. public void update(final User user) {
  13. userRepository.save(user);
  14. }
  15. }

表结构:

  1. CREATE TABLE `phone` (
  2. `id` bigint(20) NOT NULL,
  3. PRIMARY KEY (`id`)
  4. );
  5. CREATE TABLE `user` (
  6. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  7. PRIMARY KEY (`id`)
  8. )

应用配置 application.yml

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://localhost:3308/demo?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false&allowPublicKeyRetrieval=true
  4. username: root
  5. password: 123456
  6. driver-class-name: com.mysql.jdbc.Driver
  7. jpa:
  8. database-platform: org.hibernate.dialect.MySQL5Dialect
  9. hibernate:
  10. ddl-auto: validate

pom.xml 配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.1.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.example</groupId>
  12. <artifactId>demo</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>demo</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-data-jpa</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>com.google.code.findbugs</groupId>
  34. <artifactId>jsr305</artifactId>
  35. <version>2.0.1</version>
  36. </dependency>
  37. <dependency>
  38. <groupId>mysql</groupId>
  39. <artifactId>mysql-connector-java</artifactId>
  40. <version>5.1.46</version>
  41. <scope>runtime</scope>
  42. </dependency>
  43. </dependencies>
  44. <build>
  45. <plugins>
  46. <plugin>
  47. <groupId>org.springframework.boot</groupId>
  48. <artifactId>spring-boot-maven-plugin</artifactId>
  49. </plugin>
  50. </plugins>
  51. </build>
  52. </project>

调用 GET http://localhost:8080/demo 时出现错误:

org.hibernate.id.IdentifierGenerationException: attempted to assign id
from null one-to-one property [com.example.demo.Phone.user]

当我注释掉 userService.save(user); 时,代码可以正常运行并生成以下 SQL:

  1. insert into `user`
  2. values ( )
  3. -- Generated identifier: 13, using strategy: org.hibernate.id.ForeignGenerator
  4. insert into `phone` (`id`) values (?)
  5. -- Binding parameter [1] as [BIGINT] - [13]

但是如果先持久化 user,然后再更新,就会出现问题(引发上述异常)。

英文:

I work on spring boot 2.1.1.RELEASE, hibernate 5.3.7.FINAL

Rules are, that user can have no phone (phone is nullable in user) but, phone can't exist without a user (user is not null in phone).

Entities:

  1. @Entity
  2. public class Phone {
  3. @Id
  4. private Long id;
  5. @OneToOne
  6. @MapsId
  7. @JoinColumn(name = &quot;id&quot;)
  8. private User user;
  9. public Long getId() {
  10. return id;
  11. }
  12. public void setId(final Long id) {
  13. this.id = id;
  14. }
  15. public User getUser() {
  16. return user;
  17. }
  18. public void setUser(final User user) {
  19. this.user = user;
  20. }
  21. }
  22. @Entity
  23. public class User {
  24. @Id
  25. @GeneratedValue(strategy = GenerationType.IDENTITY)
  26. private Long id;
  27. @OneToOne(mappedBy = &quot;user&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
  28. private Phone phone;
  29. public Long getId() {
  30. return id;
  31. }
  32. public void setId(final Long id) {
  33. this.id = id;
  34. }
  35. public Phone getPhone() {
  36. return phone;
  37. }
  38. public void setPhone(final Phone phone) {
  39. this.phone = phone;
  40. }
  41. }

Controller:

  1. @RestController
  2. @RequestMapping
  3. public class UserController {
  4. private final UserService userService;
  5. public UserController(final UserService userService) {
  6. this.userService = userService;
  7. }
  8. @GetMapping(&quot;/demo&quot;)
  9. public void createUserAndAddPhone() {
  10. final User user = new User();
  11. userService.save(user);
  12. final Phone phone = new Phone();
  13. phone.setUser(user);
  14. user.setPhone(phone);
  15. userService.update(user);
  16. }
  17. }

Repository:

  1. public interface UserRepository extends PagingAndSortingRepository&lt;User, Long&gt; {
  2. }

Serivce:

  1. @Service
  2. public class UserService {
  3. private final UserRepository userRepository;
  4. public UserService(final UserRepository userRepository) {
  5. this.userRepository = userRepository;
  6. }
  7. @Transactional
  8. public void save(final User user) {
  9. userRepository.save(user);
  10. }
  11. @Transactional
  12. public void update(final User user) {
  13. userRepository.save(user);
  14. }
  15. }

Tables:

  1. CREATE TABLE `phone` (
  2. `id` bigint(20) NOT NULL,
  3. PRIMARY KEY (`id`)
  4. );
  5. CREATE TABLE `user` (
  6. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  7. PRIMARY KEY (`id`)
  8. )

application yml:

  1. spring:
  2. datasource:
  3. url: jdbc:mysql://localhost:3308/demo?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=UTC&amp;useLegacyDatetimeCode=false&amp;allowPublicKeyRetrieval=true
  4. username: root
  5. password: 123456
  6. driver-class-name: com.mysql.jdbc.Driver
  7. jpa:
  8. database-platform: org.hibernate.dialect.MySQL5Dialect
  9. hibernate:
  10. ddl-auto: validate

pom xml:

  1. &lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
  2. &lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  3. xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
  4. &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
  5. &lt;parent&gt;
  6. &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  7. &lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
  8. &lt;version&gt;2.1.1.RELEASE&lt;/version&gt;
  9. &lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
  10. &lt;/parent&gt;
  11. &lt;groupId&gt;com.example&lt;/groupId&gt;
  12. &lt;artifactId&gt;demo&lt;/artifactId&gt;
  13. &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
  14. &lt;name&gt;demo&lt;/name&gt;
  15. &lt;description&gt;Demo project for Spring Boot&lt;/description&gt;
  16. &lt;properties&gt;
  17. &lt;java.version&gt;1.8&lt;/java.version&gt;
  18. &lt;/properties&gt;
  19. &lt;dependencies&gt;
  20. &lt;dependency&gt;
  21. &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  22. &lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
  23. &lt;/dependency&gt;
  24. &lt;dependency&gt;
  25. &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  26. &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
  27. &lt;/dependency&gt;
  28. &lt;dependency&gt;
  29. &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  30. &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
  31. &lt;/dependency&gt;
  32. &lt;dependency&gt;
  33. &lt;groupId&gt;com.google.code.findbugs&lt;/groupId&gt;
  34. &lt;artifactId&gt;jsr305&lt;/artifactId&gt;
  35. &lt;version&gt;2.0.1&lt;/version&gt;
  36. &lt;/dependency&gt;
  37. &lt;dependency&gt;
  38. &lt;groupId&gt;mysql&lt;/groupId&gt;
  39. &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
  40. &lt;version&gt;5.1.46&lt;/version&gt;
  41. &lt;scope&gt;runtime&lt;/scope&gt;
  42. &lt;/dependency&gt;
  43. &lt;/dependencies&gt;
  44. &lt;build&gt;
  45. &lt;plugins&gt;
  46. &lt;plugin&gt;
  47. &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  48. &lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
  49. &lt;/plugin&gt;
  50. &lt;/plugins&gt;
  51. &lt;/build&gt;
  52. &lt;/project&gt;

Call GET http://localhost:8080/demo gives me an error:

> org.hibernate.id.IdentifierGenerationException: attempted to assign id
> from null one-to-one property [com.example.demo.Phone.user]

When I comment out userService.save(user);, it works and generates:

  1. insert into `user`
  2. values ( )
  3. -- Generated identifier: 13, using strategy: org.hibernate.id.ForeignGenerator
  4. insert into `phone` (`id`) values (?)
  5. -- Binding parameter [1] as [BIGINT] - [13]

but if the user is persisted and then updated, it doesn't work (raises the above exception)

答案1

得分: 2

啊,最终,这是一个 Hibernate 的 bug(HHH-12436)。

通过以下用例,在纯 Hibernate 应用程序中可以重现这个问题:

  1. Session session = sessionFactory.openSession();
  2. Transaction tr1 = session.beginTransaction();
  3. User user = new User();
  4. session.persist(user);
  5. tr1.commit();
  6. Transaction tr2 = session.beginTransaction();
  7. Phone newPhone = new Phone();
  8. user.setPhone(newPhone);
  9. newPhone.setUser(user);
  10. session.merge(user);
  11. tr2.commit();
  12. session.close();

如你可以从上面的链接看到,这个问题在 Hibernate 5.4 分支中已经修复。

附注:我能够在最新的 5.3 版本(5.3.18.Final)中复现这个问题。

英文:

Ah, in the end, this is a hibernate bug (HHH-12436).

It is reproducible in a pure hibernate application by the following use case:

  1. Session session = sessionFactory.openSession();
  2. Transaction tr1 = session.beginTransaction();
  3. User user = new User();
  4. session.persist(user);
  5. tr1.commit();
  6. Transaction tr2 = session.beginTransaction();
  7. Phone newPhone = new Phone();
  8. user.setPhone(newPhone);
  9. newPhone.setUser(user);
  10. session.merge(user);
  11. tr2.commit();
  12. session.close();

As you can see from the above link it is fixed in hibernate 5.4 branch.

P.S. I was able to reproduce the problem in the latest 5.3 version (5.3.18.Final)

huangapple
  • 本文由 发表于 2020年10月8日 23:52:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/64266219.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定