Spring JPA Hibernate带有共享主键的Oracle @OneToOne在插入时失败 - 外键冲突

时间:2013-01-13 16:13:53

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

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

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

所以基本上用户被映射到1,只有1个用户档案和用户档案只能映射回1个用户。

我在我的数据库中播种了数据,我可以使用用户配置文件读取用户对象而没有任何问题。但是当我尝试在事务中插入一个带有New UserProfile对象的新用户对象时,它在UserProfile表中失败并出现外键冲突。

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

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

以下是我的实体:

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

1 个答案:

答案 0 :(得分: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;
}