级联双向@OneToOne与@MapsId

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

Cascade on bidirectional @OneToOne with @MapsId

问题

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

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

实体类:

@Entity
public class Phone {

   @Id
   private Long id;

   @OneToOne
   @MapsId
   @JoinColumn(name = "id")
   private User user;

   public Long getId() {
       return id;
   }
   public void setId(final Long id) {
       this.id = id;
   }

   public User getUser() {
      return user;
   }
   public void setUser(final User user) {
      this.user = user;
   }
}

@Entity
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;

   @OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
   private Phone phone;

   public Long getId() {
       return id;
   }
   public void setId(final Long id) {
       this.id = id;
   }

   public Phone getPhone() {
       return phone;
   }
   public void setPhone(final Phone phone) {
       this.phone = phone;
   }
}

控制器:

@RestController
@RequestMapping
public class UserController {

   private final UserService userService;

   public UserController(final UserService userService) {
       this.userService = userService;
   }

   @GetMapping("/demo")
   public void createUserAndAddPhone() {
       final User user = new User();
       userService.save(user);
       final Phone phone = new Phone();

       phone.setUser(user);
       user.setPhone(phone);
       userService.update(user);
   }
}

仓库:

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

服务:

@Service
public class UserService {

   private final UserRepository userRepository;

   public UserService(final UserRepository userRepository) {
       this.userRepository = userRepository;
   }

   @Transactional
   public void save(final User user) {
       userRepository.save(user);
   }

   @Transactional
   public void update(final User user) {
       userRepository.save(user);
   }
}

表结构:

CREATE TABLE `phone` (
   `id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`)
);

CREATE TABLE `user` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   PRIMARY KEY (`id`)
)

应用配置 application.yml

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

pom.xml 配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.findbugs</groupId>
            <artifactId>jsr305</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</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:

insert into `user` 
values ( )

-- Generated identifier: 13, using strategy: org.hibernate.id.ForeignGenerator
                
insert into `phone` (`id`)  values (?)
-- 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:

@Entity
public class Phone {
    
   @Id
   private Long id;
    
   @OneToOne
   @MapsId
   @JoinColumn(name = &quot;id&quot;)
   private User user;
    
   public Long getId() {
       return id;
   }
   public void setId(final Long id) {
       this.id = id;
   }
    
   public User getUser() {
      return user;
   }
   public void setUser(final User user) {
      this.user = user;
   }
}
        
@Entity
public class User {

   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
    
   @OneToOne(mappedBy = &quot;user&quot;, cascade = CascadeType.ALL, orphanRemoval = true)
   private Phone phone;
    
   public Long getId() {
       return id;
   }
   public void setId(final Long id) {
       this.id = id;
   }
    
   public Phone getPhone() {
       return phone;
   }
   public void setPhone(final Phone phone) {
       this.phone = phone;
   }
}

Controller:

@RestController
@RequestMapping
public class UserController {
    
   private final UserService userService;
    
   public UserController(final UserService userService) {
       this.userService = userService;
   }
    
   @GetMapping(&quot;/demo&quot;)
   public void createUserAndAddPhone() {
       final User user = new User();
       userService.save(user);
       final Phone phone = new Phone();
    
       phone.setUser(user);
       user.setPhone(phone);
       userService.update(user);
   }
}

Repository:

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

Serivce:

@Service
public class UserService {
    
   private final UserRepository userRepository;
    
   public UserService(final UserRepository userRepository) {
       this.userRepository = userRepository;
   }
    
   @Transactional
   public void save(final User user) {
       userRepository.save(user);
   }
    
   @Transactional
   public void update(final User user) {
       userRepository.save(user);
   }
}

Tables:

CREATE TABLE `phone` (
   `id` bigint(20) NOT NULL,
   PRIMARY KEY (`id`)
);
    
CREATE TABLE `user` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   PRIMARY KEY (`id`)
)

application yml:

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

pom xml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
    &lt;project xmlns=&quot;http://maven.apache.org/POM/4.0.0&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
    	xsi:schemaLocation=&quot;http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&gt;
  	&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
   	&lt;parent&gt;
   		&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   		&lt;artifactId&gt;spring-boot-starter-parent&lt;/artifactId&gt;
   		&lt;version&gt;2.1.1.RELEASE&lt;/version&gt;
   		&lt;relativePath/&gt; &lt;!-- lookup parent from repository --&gt;
   	&lt;/parent&gt;

   	&lt;groupId&gt;com.example&lt;/groupId&gt;
   	&lt;artifactId&gt;demo&lt;/artifactId&gt;
   	&lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;
   	&lt;name&gt;demo&lt;/name&gt;
   	&lt;description&gt;Demo project for Spring Boot&lt;/description&gt;
    
   	&lt;properties&gt;
   		&lt;java.version&gt;1.8&lt;/java.version&gt;
   	&lt;/properties&gt;
    
   	&lt;dependencies&gt;
   		&lt;dependency&gt;
   			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   			&lt;artifactId&gt;spring-boot-starter&lt;/artifactId&gt;
   		&lt;/dependency&gt;
   		&lt;dependency&gt;
   			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   			&lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;
   		&lt;/dependency&gt;
   		&lt;dependency&gt;
   			&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   			&lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;
   		&lt;/dependency&gt;
   		&lt;dependency&gt;
   			&lt;groupId&gt;com.google.code.findbugs&lt;/groupId&gt;
   			&lt;artifactId&gt;jsr305&lt;/artifactId&gt;
   			&lt;version&gt;2.0.1&lt;/version&gt;
   		&lt;/dependency&gt;
   		&lt;dependency&gt;
   			&lt;groupId&gt;mysql&lt;/groupId&gt;
   			&lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
   			&lt;version&gt;5.1.46&lt;/version&gt;
  			&lt;scope&gt;runtime&lt;/scope&gt;
   		&lt;/dependency&gt;
   	&lt;/dependencies&gt;
    
   	&lt;build&gt;
   		&lt;plugins&gt;
   			&lt;plugin&gt;
   				&lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
   				&lt;artifactId&gt;spring-boot-maven-plugin&lt;/artifactId&gt;
   			&lt;/plugin&gt;
   		&lt;/plugins&gt;
   	&lt;/build&gt;
&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:

insert into `user` 
values ( )

-- Generated identifier: 13, using strategy: org.hibernate.id.ForeignGenerator
                
insert into `phone` (`id`)  values (?)
-- 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 应用程序中可以重现这个问题:

Session session = sessionFactory.openSession();

Transaction tr1 = session.beginTransaction();
User user = new User();
session.persist(user);
tr1.commit();

Transaction tr2 = session.beginTransaction();
Phone newPhone = new Phone();
user.setPhone(newPhone);
newPhone.setUser(user);
session.merge(user);
tr2.commit();

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:

Session session = sessionFactory.openSession();

Transaction tr1 = session.beginTransaction();
User user = new User();
session.persist(user);
tr1.commit();

Transaction tr2 = session.beginTransaction();
Phone newPhone = new Phone();
user.setPhone(newPhone);
newPhone.setUser(user);
session.merge(user);
tr2.commit();

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:

确定