OneToOne между двумя таблицами с общим первичным ключом
Я пытаюсь настроить следующие таблицы с помощью JPA/Hibernate:
User:
userid - PK
name
Validation:
userid - PK, FK(user)
code
Может быть много пользователей, и у каждого пользователя может быть максимум один код проверки или нет.
Здесь мои классы:
public class User
{
@Id
@Column(name = "userid")
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long userId;
@Column(name = "name", length = 50, unique = true, nullable = false)
protected String name;
...
}
public class Validation
{
@Id
@Column(name = "userid")
protected Long userId;
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
@Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
Я создаю пользователя, а затем попытаюсь добавить код проверки, используя следующий код:
public void addValidationCode(Long userId)
{
EntityManager em = createEntityManager();
EntityTransaction tx = em.getTransaction();
try
{
tx.begin();
// Fetch the user
User user = retrieveUserByID(userId);
Validation validation = new Validation();
validation.setUser(user);
em.persist(validation);
tx.commit();
}
...
}
Когда я пытаюсь запустить его, я получаю org.hibernate.PersistentObjectException: удаленный объект, переданный для сохранения: User
Я также попытался использовать следующий код в моем классе Validation:
public void setUserId(Long userId)
{
this.userId = userId;
}
и когда я создаю код проверки, я просто делаю:
Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();
Но тогда, когда пользователь имеет значение null, я получаю org.hibernate.PropertyValueException: свойство not-null ссылается на значение null или переходное: User.code
Порадовал бы любую помощь относительно того, как лучше всего решить эту проблему!
Ответы
Ответ 1
Если вы используете Hibernate, вы также можете использовать
public class Validation {
private Long validationId;
private User user;
@Id
@GeneratedValue(generator="SharedPrimaryKeyGenerator")
@GenericGenerator(name="SharedPrimaryKeyGenerator",strategy="foreign",parameters = @Parameter(name="property", value="user"))
@Column(name = "VALIDATION_ID", unique = true, nullable = false)
public Long getValidationId(){
return validationId;
}
@OneToOne
@PrimaryKeyJoinColumn
public User getUser() {
return user;
}
}
Hibernate будет убедиться, что идентификатор проверки будет таким же, как идентификатор набора пользовательских объектов.
Ответ 2
Мне удалось решить эту проблему "OneToOne между двумя таблицами с общим первичным ключом" в чистом JPA 2.0 (благодаря многим существующим потокам в SOF). Фактически в JPA есть два способа справиться с этим. Я использовал eclipselink как поставщик JPA и MySql как базу данных. Чтобы еще раз подчеркнуть, что здесь не использовались проприетарные классы eclipselink.
-
Первый подход заключается в использовании стратегии типа генерации AUTO в поле Идентификатор родительского сущности.
-
Родительская сущность должна содержать член типа дочернего объекта в отношениях OneToOne (тип каскада PERSIST и mappedBy = член родительского типа сущности дочернего объекта)
@Entity
@Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="USER_ID")
private Integer userId;
@OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
-
Child Entity не должен содержать поле идентификатора. Он должен содержать член типа родительского объекта с аннотациями Id, OneToOne и JoinColumn. JoinColumn должен указывать имя поля идентификатора таблицы DB.
@Entity
@Table(name = "USER_DETAIL")
public class UserDetail implements Serializable {
@Id
@OneToOne
@JoinColumn(name="USER_ID")
private UserLogin userLogin;
// getters & setters
}
-
Вышеприведенный подход внутренне использует таблицу DB по умолчанию с именем SEQUENCE для назначения значений в поле идентификатора. Если это еще не указано, эту таблицу необходимо создать, как показано ниже.
DROP TABLE TEST.SEQUENCE ;
CREATE TABLE TEST.SEQUENCE (SEQ_NAME VARCHAR(50), SEQ_COUNT DECIMAL(15));
INSERT INTO TEST.SEQUENCE(SEQ_NAME, SEQ_COUNT) values ('SEQ_GEN', 0);
-
Второй подход заключается в использовании настраиваемой стратегии типа TABLE и аннотации TableGenerator в поле Идентификатор родительской сущности.
-
За исключением изменений в поле идентификатора, все остальное остается неизменным в родительской сущности.
@Entity
@Table(name = "USER_LOGIN")
public class UserLogin implements Serializable {
@Id
@TableGenerator(name="tablegenerator", table = "APP_SEQ_STORE", pkColumnName = "APP_SEQ_NAME", pkColumnValue = "USER_LOGIN.USER_ID", valueColumnName = "APP_SEQ_VALUE", initialValue = 1, allocationSize = 1 )
@GeneratedValue(strategy = GenerationType.TABLE, generator = "tablegenerator")
@Column(name="USER_ID")
private Integer userId;
@OneToOne(cascade = CascadeType.PERSIST, mappedBy = "userLogin")
private UserDetail userDetail;
// getters & setters
}
-
В дочернем объекте изменений нет. Он остается таким же, как и в первом подходе.
-
Этот подход генератора таблиц внутренне использует таблицу APP_SEQ_STORE для назначения значений в поле идентификатора. Эта таблица должна быть создана, как показано ниже.
DROP TABLE TEST.APP_SEQ_STORE;
CREATE TABLE TEST.APP_SEQ_STORE
(
APP_SEQ_NAME VARCHAR(255) NOT NULL,
APP_SEQ_VALUE BIGINT NOT NULL,
PRIMARY KEY(APP_SEQ_NAME)
);
INSERT INTO TEST.APP_SEQ_STORE VALUES ('USER_LOGIN.USER_ID', 0);
Ответ 3
Используете ли вы JPA или JPA 2.0?
Если Validation PK является FK для пользователя, тогда вам не нужен атрибут Long userId в классе проверки, но вместо этого нужно сделать аннотацию @Id
. Это будет:
Public class Validation
{
@Id
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn(name = "userid", referencedColumnName = "userid")
protected User user;
@Column(name = "code", length = 10, unique = true, nullable = false)
protected String code;
...
public void setUser(User user)
{
this.user = user;
this.userId = user.getUserId();
}
...
}
Попробуйте с этим и сообщите нам свои результаты.
Ответ 4
Вам нужно установить как userId
, так и user
.
Если вы установите только user
, то id
для Validation
равно 0 и считается отсоединенным. Если вы установите только userId
, то вам нужно сделать свойство user
nullable, что здесь не имеет смысла.
Чтобы быть в безопасности, вы можете установить их как в одном вызове метода:
@Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
Я пометил метод @Transient
, чтобы Hibernate проигнорировал его. Кроме того, вы все еще можете работать setUser
и setUserId
с любыми "побочными эффектами".