Предположим, что у меня есть два объекта (ommitted getters / setters):
@Entity
@Table
public class A {
private Long id;
private B b;
private String details;
@SequenceGenerator(name = "auto_gen", sequenceName = "a_sequence", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "auto_gen")
@Column(name="id",updatable = false,nullable = false)
@Id
public Long getId(){return id;}
@OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
@JoinColumn(name = "a_id")
public B getB(){
return b;
}
}
@Entity
@Table
public class B {
@Id
@GenericGenerator(name="gen",strategy = "BIdentityGenerator")
@GeneratedValue(generator = "gen")
private Long id;
@Column
private String reason;
@Column
private Boolean requiresDetails;
}
И обе таблицы связаны так:
Table A
-------
id
b_id (foreign key to primary key in B)
details
Table B
-------
id
reason (unique)
В таблице B хранятся статические данные , поэтому новые строки никогда не должны быть вставлены. Учитывая, что у меня есть новый объект A и отношение сущности к существующему объекту Bb B, я хотел бы сохранить A как новую строку и ссылку на существующую строку для таблицы B. Я также хотел бы сохранить A без идентификатора B, только уникальное значение reason.
Этот сценарий прекрасен, если я вручную предоставил идентификатор для B перед сохранением A, но кодирование этого способа кажется немного громоздким. Мне было интересно, есть ли способ сделать это через генератор id, чтобы неявно установить идентификатор B, заданный значением reason в базе данных. Я попытался использовать GenericGenerator для генерации идентификатора (см. Ниже); однако в этой ситуации возникает проблема, когда предполагается, что сгенерированное значение является полностью новым сгенерированным идентификатором и нарушает ограничение первичного ключа. Я также рассмотрел использование слушателей сущностей, но в документах указано, что они не должны называть EntityManager.
public class BIdentityGenerator implements IdentifierGenerator {
private static final String QUERY = "SELECT id FROM b WHERE reason = ?";
private static Logger log = LoggerFactory.getLogger(BIdentityGenerator.class);
@Override
public Serializable generate(SharedSessionContractImplementor session, Object o) throws HibernateException {
if (o instanceof B) {
B b = (B) o;
if (b.getReason() == null || b.getReason().isEmpty()) {
throw new HibernateException("No reason provided.");
}
try {
Connection connection = session.connection();
PreparedStatement statement = connection.prepareStatement(QUERY);
statement.setString(1, b.getReason());
ResultSet resultSet = statement.executeQuery();
if (resultSet.next()) {
long id = resultSet.getLong("id");
return id;
} else {
throw new HibernateException(String.format("No row with reason %s exists.", b.getReason()));
}
} catch (SQLException e) {
throw new HibernateException(String.format("Unable to retrieve reason %s", b),e);
}
} else {
throw new HibernateException("BIdentityGenerator only supports B.");
}
}
}
вы должны удалить CascadeType.ALL из Entity A. у вас должен быть DAO, который возвращает вас Entity B на основе reason, что-то вроде entityA.setEntityB(entityBDao.findBy(reason))