比方说,我有两个实体(省略了获取器/设置器):
@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和与现有db对象B的实体关系,我想将A保留为新行并链接到表B的现有行。我也想保留A而没有id事先为B,只有唯一的reason
值。
如果在持久化A之前为B手动提供一个ID,这种情况很好,但是以这种方式编码似乎有点麻烦。我想知道是否有一种方法可以通过id生成器在数据库中给定reason
的值的情况下隐式设置B的id。
我曾尝试使用GenericGenerator生成ID(请参见下文);但是,这有一个问题,即它假定生成的值是一个全新的生成的id,并且违反了主键约束。我还考虑过使用实体侦听器,但是文档指出它们不应调用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.");
}
}
}
答案 0 :(得分:0)
您应该从实体A中删除CascadeType.ALL
。
您应该有DAO,它根据reason
返回实体B,类似于entityA.setEntityB(entityBDao.findBy(reason))
答案 1 :(得分:0)
鉴于您介绍的数据库设置,此注释:
@JoinColumn(name = "a_id")
拼写错误。您似乎是故意b_id
。
后来,您写道:
表B保存了静态数据,因此永远不要插入新行。
,我想你的意思是,现有行也不应该更新或删除。在这种情况下,您不应将 any 操作从实体A
层叠到实体B
,如果未指定,这是默认设置。也就是说,只需使用此即可映射关系:
@OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "b_id")
主要问题似乎是这样的:
如果我之前手动为B提供了一个ID,这种情况很好 保留A,但是用这种方式编码似乎有点麻烦。我曾是 想知道是否有一种方法可以通过id生成器 给定数据库中的
reason
值,隐式设置B的ID。
如果“为B提供ID”是指提供B ,那么这正是您应该做的。这就是JPA中实体关系的工作方式:通过实体实例(myA.setB(someB)
)。另一方面,如果您想出一种直接在表A中设置b_id
的机制,那么可以,我可以想象这很麻烦。
无论如何,否,您不能为此使用ID生成器。首先,这是错误的工具。它们旨在提供唯一的ID,这是您想要的最后一件事。第二,正如我已经介绍的那样,您不想设置B的ID,而是想要设置B。
由于B实体的集合是静态的,因此您应该考虑在程序启动时将 all 个B加载到内存中,将它们保存在内部映射中,并从中进行绘制以将B分配给As 。只要您关闭从A到B的级联,是否允许它们分离都没关系。