【问题标题】:Spring JPA Hibernate Oracle @OneToOne with Shared Primary Key fails on insert - foreign key violation具有共享主键的 Spring JPA Hibernate Oracle @OneToOne 在插入时失败 - 外键违规
【发布时间】:2013-01-13 16:13:52
【问题描述】:

我尝试使用 @OneToOne 关系设置 2 个实体。

我使用 Oracle 11g 作为数据库,并且用户表具有通过序列生成的 ID。用户表中的这个 Id 是 UserProfile 表中的主键和外键。

所以基本上一个用户被映射到 1 个并且只有 1 个用户配置文件和一个用户配置文件映射回只有 1 个用户。

我已经在我的数据库中播种了数据,我可以毫无问题地读取带有用户配置文件的用户对象。但是,当我尝试在事务中插入带有新 UserProfile 对象的新用户对象时,它会因我的 UserProfile 表中的外键冲突而失败。

使用共享主键的@OneToOne 似乎是一个常见问题,但我找不到有效的答案。有人能指出我正确的方向吗?

我可以看到 USerProfile 中的 Id 设置正确,只是在提交到数据库时它会引发违规。似乎用户配置文件在用户之前被提交到数据库。有没有办法先持久化用户而不必单独持久化这些对象?

这是我的实体:

package com.company.ca.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;


@Entity
@Table(name="USERS")
@SequenceGenerator(name="USERS_SEQ", sequenceName="USERS_SEQ", allocationSize=1)
public class Users implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator="USERS_SEQ")
private Long id;
@Column(name="USERNAME", unique=true)
private String email;
private String password;

@Column(name="CREATED_DATE", insertable=false, updatable=false)
private Date createdDate;
@Column(name="CREATED_BY")
private String createdBy;
@Column(name="LAST_UPDATED_DATE")
private Date lastUpdatedDate;
@Column(name="LAST_UPDATED_BY")
private String lastUpdatedBy;
private int enabled;

@OneToOne(cascade = CascadeType.ALL, mappedBy="user")
@PrimaryKeyJoinColumn(name="ID", referencedColumnName="USER_ID")
private UsersProfile usersProfile;

public Users() {
}

public Long getId() {
    return id;
}

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

public String getEmail() {
    return email;
}

public void setEmail(String email) {
    this.email = email;
}

public String getPassword() {
    return password;
}

public void setPassword(String password) {
    this.password = password;
}

public int getEnabled() {
    return enabled;
}

public void setEnabled(int enabled) {
    this.enabled = enabled;
}

public Date getCreatedDate() {
    return createdDate;
}

public void setCreatedDate(Date createdDate) {
    this.createdDate = createdDate;
}

public String getCreatedBy() {
    return createdBy;
}

public void setCreatedBy(String createdBy) {
    this.createdBy = createdBy;
}

public Date getLastUpdatedDate() {
    return lastUpdatedDate;
}

public void setLastUpdatedDate(Date lastUpdatedDate) {
    this.lastUpdatedDate = lastUpdatedDate;
}

public String getLastUpdatedBy() {
    return lastUpdatedBy;
}

public void setLastUpdatedBy(String lastUpdatedBy) {
    this.lastUpdatedBy = lastUpdatedBy;
}

public UsersProfile getUsersProfile() {
    return usersProfile;
}

public void setUsersProfile(UsersProfile usersProfile) {
    this.usersProfile = usersProfile;
}
}

package com.company.ca.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;

@Entity
@Table(name="USERS_PROFILE")
@org.hibernate.annotations.GenericGenerator(name="user-primarykey", strategy="foreign",
    parameters={@org.hibernate.annotations.Parameter(name="property", value="user")
})
public class UsersProfile implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(generator = "user-primarykey")
@Column(name = "USER_ID")
private Long userId;

@OneToOne
@PrimaryKeyJoinColumn(name="USER_ID", referencedColumnName="ID")
private Users user;

@Column(name="FIRST_NAME")
private String firstName;
@Column(name="LAST_NAME")
private String lastName;
@Column(name="PHONE_NUMBER")
private String phoneNumber;
@Column(name="CREATED_DATE", insertable=false, updatable=false)
private Date createdDate;
@Column(name="CREATED_BY")
private String createdBy;
@Column(name="LAST_UPDATED_DATE")
private Date lastUpdatedDate;
@Column(name="LAST_UPDATED_BY")
private String lastUpdatedBy;

public UsersProfile() {
    super();
}

public String getFirstName() {
    return firstName;
}

public void setFirstName(String firstName) {
    this.firstName = firstName;
}

public String getLastName() {
    return lastName;
}

public void setLastName(String lastName) {
    this.lastName = lastName;
}

public String getPhoneNumber() {
    return phoneNumber;
}

public void setPhoneNumber(String phoneNumber) {
    this.phoneNumber = phoneNumber;
}

public Users getUser() {
    return user;
}

public void setUser(Users user) {
    this.user = user;
}

public Date getCreatedDate() {
    return createdDate;
}

public void setCreatedDate(Date createdDate) {
    this.createdDate = createdDate;
}

public String getCreatedBy() {
    return createdBy;
}

public void setCreatedBy(String createdBy) {
    this.createdBy = createdBy;
}

public Date getLastUpdatedDate() {
    return lastUpdatedDate;
}

public void setLastUpdatedDate(Date lastUpdatedDate) {
    this.lastUpdatedDate = lastUpdatedDate;
}

public String getLastUpdatedBy() {
    return lastUpdatedBy;
}

public void setLastUpdatedBy(String lastUpdatedBy) {
    this.lastUpdatedBy = lastUpdatedBy;
}

public Long getUserId() {
    return userId;
}

public void setUserId(Long userId) {
    this.userId = userId;
}
}

这是我的 DAO 和 JUnit:

package com.company.ca.persistence;

import org.springframework.data.repository.CrudRepository;

import com.company.ca.domain.Users;


public interface UserDAO extends CrudRepository<Users, Long> {

}


package com.company.ca.persistence.test;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;

import com.company.ca.domain.Users;
import com.company.ca.domain.UsersProfile;
import com.company.ca.persistence.UserDAO;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
(
        locations = {
                "classpath*:/spring-persistence.xml"
        } 
)
@TransactionConfiguration(defaultRollback=false)
@Service
public class UserDAOImplTest extends AbstractTransactionalJUnit4SpringContextTests {


protected final Log logger = LogFactory.getLog(getClass());

@Autowired
UserDAO userDAO;

@Test
public void testFindUser() {
    Users testUser = userDAO.findOne(new Long(2));
    Assert.assertNotNull("User Object should not be null", testUser);
    Assert.assertNotNull("User Profile should not be null", testUser.getUsersProfile());
}

@Test
//@Transactional(propagation=Propagation.MANDATORY)
public void testCreateUser() {
    String email = "test.user2@testCompany.com";
    String firstName = "Test 2";
    String lastName = "User";
    String phoneNumber = "1234567890";
    String createdBy = "IT_SYSTEM@system.com";
    String password = "password";

    // now lets create the user
    Users user = new Users();
    user.setCreatedBy(createdBy);
    user.setPassword(password);
    user.setEmail(email);
    user.setEnabled(0);

    UsersProfile userProfile = new UsersProfile();
    userProfile.setCreatedBy(createdBy);
    userProfile.setFirstName(firstName);
    userProfile.setLastName(lastName);
    userProfile.setPhoneNumber(phoneNumber);
    userProfile.setUser(user);

    user.setUsersProfile(userProfile);

    userDAO.save(user);
    Assert.assertNotNull("UserId was not returned...", user.getId());
    Assert.assertEquals("Expected email not set correctly", email, 

user.getEmail());
    }
}


<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"    
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    
    xmlns:jdbc="http://www.springframework.org/schema/jdbc" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xsi:schemaLocation="http://www.springframework.org/schema/beans              
    http://www.springframework.org/schema/beans/spring-beans-3.1.xsd              
    http://www.springframework.org/schema/jdbc              
    http://www.springframework.org/schema/jdbc/spring-jdbc-3.1.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
    http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context-3.1.xsd
    http://www.springframework.org/schema/data/jpa 
    http://www.springframework.org/schema/data/jpa/spring-jpa.xsd"> 


<context:annotation-config />
<context:property-placeholder location="classpath*:/persistence.properties" />

<jpa:repositories base-package="com.company.ca.persistence"/>

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${database.driver}" />
    <property name="url" value="${database.url}" />
    <property name="username" value="${database.username}" />
    <property name="password" value="${database.password}" />
</bean>         

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="packagesToScan" value="com.company.ca.domain" />
  <property name="persistenceProviderClass" value="org.hibernate.ejb.HibernatePersistence" />
  <property name="loadTimeWeaver">
    <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver" />
  </property>
  <property name="jpaProperties">
    <props>
      <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
      <prop key="hibernate.max_fetch_depth">5</prop>
      <prop key="hibernate.jdbc.fetch_size">50</prop>
      <prop key="hibernate.jdbc.batch_size">10</prop>
      <prop key="hibernate.show_sql">true</prop>
    </props>
  </property>
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

这是我的输出:

10:28:13,400 DEBUG SQL:104 - select USERS_SEQ.nextval from dual
Hibernate: select USERS_SEQ.nextval from dual
10:28:13,500 DEBUG SQL:104 - insert into USERS (CREATED_BY, USERNAME, enabled, LAST_UPDATED_BY, LAST_UPDATED_DATE, password, id) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into USERS (CREATED_BY, USERNAME, enabled, LAST_UPDATED_BY, LAST_UPDATED_DATE, password, id) values (?, ?, ?, ?, ?, ?, ?)
10:28:13,501 TRACE BasicBinder:83 - binding parameter [1] as [VARCHAR] - VRS_SYSTEM@audatex.com
10:28:13,501 TRACE BasicBinder:83 - binding parameter [2] as [VARCHAR] - colin.moore3@audatex.com
10:28:13,502 TRACE BasicBinder:83 - binding parameter [3] as [INTEGER] - 0
10:28:13,502 TRACE BasicBinder:71 - binding parameter [4] as [VARCHAR] - <null>
10:28:13,504 TRACE BasicBinder:71 - binding parameter [5] as [TIMESTAMP] - <null>
10:28:13,505 TRACE BasicBinder:83 - binding parameter [6] as [VARCHAR] - test
10:28:13,505 TRACE BasicBinder:83 - binding parameter [7] as [BIGINT] - 117
10:28:13,577 DEBUG SQL:104 - insert into USERS_PROFILE (CREATED_BY, FIRST_NAME, LAST_NAME, LAST_UPDATED_BY, LAST_UPDATED_DATE, PHONE_NUMBER, USER_ID) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into USERS_PROFILE (CREATED_BY, FIRST_NAME, LAST_NAME, LAST_UPDATED_BY, LAST_UPDATED_DATE, PHONE_NUMBER, USER_ID) values (?, ?, ?, ?, ?, ?, ?)
10:28:13,579 TRACE BasicBinder:83 - binding parameter [1] as [VARCHAR] - VRS_SYSTEM@audatex.com
10:28:13,579 TRACE BasicBinder:83 - binding parameter [2] as [VARCHAR] - Colin
10:28:13,579 TRACE BasicBinder:83 - binding parameter [3] as [VARCHAR] - Moore
10:28:13,580 TRACE BasicBinder:71 - binding parameter [4] as [VARCHAR] - <null>
10:28:13,580 TRACE BasicBinder:71 - binding parameter [5] as [TIMESTAMP] - <null>
10:28:13,580 TRACE BasicBinder:83 - binding parameter [6] as [VARCHAR] - 4164983787
10:28:13,581 TRACE BasicBinder:83 - binding parameter [7] as [BIGINT] - 117
10:28:13,715  WARN SqlExceptionHelper:143 - SQL Error: 2291, SQLState: 23000
10:28:13,716 ERROR SqlExceptionHelper:144 - ORA-02291: integrity constraint (SYSTEM.FK_USER_ID) violated - parent key not found

10:28:13,716  WARN SqlExceptionHelper:143 - SQL Error: 2291, SQLState: 23000
10:28:13,716 ERROR SqlExceptionHelper:144 - ORA-02291: integrity constraint (SYSTEM.FK_USER_ID) violated - parent key not found

10:28:13,719 ERROR BatchingBatch:119 - HHH000315: Exception executing batch [ORA-02291: integrity constraint (SYSTEM.FK_USER_ID) violated - parent key not found

【问题讨论】:

  • 数据库中是否有这些测序仪的触发器?
  • 是的,用户表存在触发器。 UserProfile 表没有触发器,因为主键是 User 表的外键。我怀疑 UserProfile 对象试图在 User 对象之前持久化。但不知道如何改正。
  • 它是引用表的外键,但它是自己表的主键。尝试将触发器放在两侧。
  • 那么 UserProfile 表上的触发器会采用与 User 表相同的 id 吗?在 Hibernate 中,有没有办法在插入时不映射 id,而让数据库处理 id 生成。现在休眠通过 User 对象上的序列设置 id(来自我的 Oracle DB 中的 User 序列)
  • 您找到解决方案了吗?我在使用 MySQL 和 JPA 时遇到了类似的问题。在我的数据模型中,BlogBlogVoteCounter之间存在双向关系,它们共享主键,主键也作为外键相互引用。

标签: spring hibernate jpa oracle11g one-to-one


【解决方案1】:

您的映射错误。在双向关联中,一侧是关联的所有者,并说明关联是如何映射的。另一边是相反的一面,只是使用mappedBy 属性说“看另一边的映射”。

参见the documentation 示例:

@Entity
class UserProfile {
    @Id Integer id;

    @MapsId @OneToOne
    @JoinColumn(name = "user_id")
    User user;
}

@Entity
class Person {
    @Id @GeneratedValue 
    Integer id;

    @OneToOne(mappedBy = "user")
    private UserProfile profile;
}

【讨论】:

  • 感谢您的回复。我按照您的建议进行了调整,但并没有解决我的问题。我的问题是在数据库级别。在 UserProfile 中,我的主键也是用户表的外键(因此它们具有共享主键)。我遇到的异常是当休眠尝试提交到数据库时,抛出外键冲突(父是子试图在同一个事务中创建。有什么想法吗?
  • 外键约束在user_profile表中,而不是在user表中,对吧?如果不是,那就是问题所在。
  • 正确,我在 UserProfile 表中的 USER_ID 既是用户表中 ID 的主键也是外键。我会假设休眠将支持在同一事务中创建用户和用户配置文件,也许我错了?
  • 您提供的映射是正确的,它没有解决我的问题。我的问题是在数据库级别,更具体地说是我在 User 表上的 Trigger。我的用户类正在使用 Oracle 的序列映射用户的 ID,但是在插入时,这个 ID 被我的触发器覆盖(在我的触发器中没有空检查)。因此,用户将使用 Userprofile 不知道的不同 Id 持久化。因此,在修复了我的触发器之后,现在一切正常。课程 - 三重检查数据库;-)
猜你喜欢
  • 1970-01-01
  • 2012-09-02
  • 1970-01-01
  • 2014-10-12
  • 2018-02-02
  • 1970-01-01
  • 2016-05-26
  • 2020-02-19
  • 1970-01-01
相关资源
最近更新 更多