#java #database #hibernate #jpa
#java #База данных #переход в спящий режим #jpa
Вопрос:
Я пытаюсь настроить следующие таблицы, используя 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: отдельная сущность, переданная в persist: User
Я также пытался использовать следующий код в своем классе проверки:
public void setUserId(Long userId)
{
this.userId = userId;
}
и когда я создаю код проверки, я просто делаю:
Validation validation = new Validation();
validation.setUserId(userId);
em.persist(validation);
tx.commit();
Но затем, поскольку User равен null, я получаю org.hibernate.Исключение PropertyValueException: свойство not-null ссылается на нулевое или временное значение: User.code
Был бы признателен за любую помощь относительно того, как наилучшим образом решить эту проблему!
Ответ №1:
Я смог решить эту проблему «OneToOne между двумя таблицами с общим первичным ключом» чистым способом JPA 2.0 (благодаря множеству существующих потоков на SOF). На самом деле в JPA есть два способа справиться с этим. Я использовал eclipselink в качестве поставщика JPA и MySQL в качестве базы данных. Чтобы еще раз подчеркнуть, что здесь не использовались проприетарные классы eclipselink.
-
Первый подход заключается в использовании стратегии автоматической генерации типа в поле идентификатора родительского объекта.
-
Родительская сущность должна содержать элемент типа дочерней сущности в отношениях OneToOne (каскадный тип СОХРАНЯЕТСЯ и 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 amp; setters }
-
Дочерний объект не должен содержать поле идентификатора. Он должен содержать элемент типа родительской сущности с аннотациями Id, OneToOne и JoinColumn. В JoinColumn должно быть указано имя поля ID таблицы базы данных.
@Entity @Table(name = "USER_DETAIL") public class UserDetail implements Serializable { @Id @OneToOne @JoinColumn(name="USER_ID") private UserLogin userLogin; // getters amp; setters }
-
Описанный выше подход внутренне использует таблицу БД по умолчанию с именем 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);
-
-
Второй подход заключается в использовании настраиваемой стратегии создания типов ТАБЛИЦ и аннотации 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 amp; setters }
-
В дочернем объекте изменений нет. Это остается таким же, как и в первом подходе.
-
Этот подход к созданию таблиц внутренне использует таблицу DB 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);
-
Комментарии:
1. первый подход не приводит к бесконечному циклу при попытке загрузить UserLogin?
Ответ №2:
Если вы используете режим гибернации, вы также можете использовать
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 гарантирует, что идентификатор проверки будет таким же, как идентификатор пользовательского набора объектов.
Ответ №3:
Используете ли вы JPA или JPA 2.0?
Если проверка 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();
}
...
}
Попробуйте с этим и сообщите нам свои результаты.
Комментарии:
1. Как вы можете задать
userId
поле вsetUser()
, если вы его не определяете?
Ответ №4:
Вам нужно установить оба userId
и user
.
Если вы задаете только user
, то id
for Validation
равно 0 и считается отсоединенным. Если вы задаете только userId
, то вам нужно сделать user
свойство обнуляемым, что здесь не имеет смысла.
На всякий случай вы, вероятно, можете установить их оба в одном вызове метода:
@Transient
public void setUserAndId(User user){
this.userId = user.getId();
this.user = user;
}
Я пометил метод @Transient
, чтобы Hibernate игнорировал его. Кроме того, таким образом, вы все еще можете иметь setUser
и setUserId
работать, как ожидалось, без каких-либо «побочных эффектов».