@OneToOne从两个班级到同一班级

时间:2017-08-08 15:09:51

标签: java jpa spring-boot spring-data spring-data-jpa

为了更好地理解,我想实现这个目标:

External Account example

注意:买方可能没有ExternalAccount,但卖方必须拥有它。我有/尝试过:

买方类:

@Entity
public class Buyer extends User {

    @OneToOne(optional=true, cascade= {CascadeType.MERGE})
    @JoinColumn(nullable=true)
    private ExternalAccount externalAccount;
    //getters and setters

}

卖方类:

@Entity
public class Seller extends User {

    @OneToOne(optional=false, cascade= {CascadeType.MERGE})
    @MapsId
    @JoinColumn(nullable=false)
    private ExternalAccount externalAccount;
    //getters and setters and other properties

} 

ExternalAccount类:

@Entity
public class ExternalAccount {

    @Id
    @PrimaryKeyJoinColumn
    private Long id;
    //getters and setters

}

我正在使用Spring Data JPA和Spring Boot,我希望如此:

  • 如果没有与买方相关但存在ExternalAccount(与卖方关联),请将其关联。

  • 如果没有与卖方相关但存在ExternalAccount(与买方关联),请将其关联。

  • 如果不存在ExternalAccount,则在保存买方/卖方时,会创建ExternalAccount。

我可以用CascadeType.MERGE实现类似的行为(在阅读了很多Stackoverflow的帖子之后),但是使用它时它不尊重@OneToOne映射。它允许创建许多与相同ExternalAccount相关的买家。

  • 我用数据库测试创建了一个github项目来重现这个问题。

https://github.com/ralphavalon/jpa-mapping

在那里,我有我的示例休息控制器(MappingController):

//Creating buyer example
@RequestMapping(value = "/newBuyer", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object newBuyer() {
    Buyer buyer = new Buyer();
    buyer.setBirthdate(LocalDateTime.now());
    buyer.setEmail("buyer@email.com");
    buyer.setName("Buyer Name");
    ExternalAccount external = new ExternalAccount();
    external.setId(123L);
    buyer.setExternalAccount(external);
    buyerDao.save(buyer);
    return buyer;
}

//Creating seller example
@RequestMapping(value = "/newSeller", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Object newSeller() {
    Seller seller = new Seller();
    seller.setBirthdate(LocalDateTime.now());
    seller.setEmail("seller@email.com");
    seller.setName("Seller Name");
    ExternalAccount external = new ExternalAccount();
    external.setId(123L);
    seller.setExternalAccount(external);
    sellerDao.save(seller);
    return seller;
}

第一次拨打/newBuyer时,会保存。现在,如果我在调用/newSeller后致电/newBuyer,则会返回此信息:

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.EXTERNAL_ACCOUNT(ID)"; SQL statement:
insert into external_account (id) values (?) [23505-196]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause

org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.EXTERNAL_ACCOUNT(ID)"; SQL statement:
insert into external_account (id) values (?) [23505-196]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:345) ~[h2-1.4.196.jar:1.4.196]

1 个答案:

答案 0 :(得分:0)

我解决了这些变化的问题:

  • 更改类的映射:

买方类:

@Entity
public class Buyer extends User {

    @OneToOne(optional=true, cascade= {CascadeType.MERGE})
    @JoinColumn(nullable=true, unique=true)
    private ExternalAccount externalAccount;
    //getters and setters

}

卖家类:

@Entity
public class Seller extends User {

    @OneToOne(optional=false, cascade= {CascadeType.MERGE})
    @JoinColumn(nullable=false, unique=true)
    private ExternalAccount externalAccount;
    //getters and setters

}

ExternalAccount 类:

@Entity
public class ExternalAccount {

    @Id
    private Long id;
    //getters and setters

}

最重要的部分:覆盖Spring Data JPA保存方法以使用entityManager.merge

@Service
public class BuyerService {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public Buyer save(Buyer buyer) {
        return entityManager.merge(buyer);
    }

}

和SellerService一样。