Java 持久化时引用不带外键的静态表
假设我有两个实体(ommitted getter/setter): 这两个表是这样链接的:Java 持久化时引用不带外键的静态表,java,hibernate,jpa,Java,Hibernate,Jpa,假设我有两个实体(ommitted getter/setter): 这两个表是这样链接的: 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,而不必事先具有B的id,只保留唯一的原因
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,而不必事先具有B的id,只保留唯一的原因
值
如果我在持久化A之前手动为B提供一个id,这个场景就可以了,但是这种编码方式似乎有点麻烦。我想知道是否有一种方法可以通过id生成器隐式设置B的id,给定数据库中reason
的值。
我尝试使用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.");
}
}
}
您应该从实体A中删除
CascadeType.ALL
。
您应该有一个DAO,它根据
reason
返回实体B,类似于entityA.setEntityB(entityBDao.findBy(reason))
给定您介绍的数据库设置,此注释:
是拼错了。您的意思似乎是b_id
后来,你写道:
表B保存静态数据,因此永远不应该插入新行
我想你的意思是,现有的行也不应该被更新或删除。在这种情况下,您不应该将任何操作从实体A
级联到实体B
,如果不指定,这是默认操作。也就是说,只需使用以下命令即可映射关系:
然而,主要问题似乎是:
如果我在之前手动为B提供一个id,这个场景就可以了
持久化,但这种编码方式似乎有点麻烦。我是
想知道是否有一种方法可以通过id生成器
根据数据库中reason
的值隐式设置B的id
如果“为B提供id”是指提供B,那么这正是你应该做的。这就是JPA中实体关系的工作方式:通过实体实例(myA.setB(someB)
)。另一方面,如果您在表A中直接提出了一些设置b_id
的机制,那么是的,我可以想象这会很麻烦
在任何情况下,都不能使用ID生成器进行此操作。首先,这是一个错误的工具。这些都是为了提供独特的ID而设计的,这是您最不想要的。第二,正如我已经介绍过的,您不想设置ab的ID,您想设置ab
由于B实体的集合是静态的,您应该考虑在程序启动时将所有BS加载到内存中,将它们保存在内部映射中,并从那些映射到您的AS中分配BS。不管您是否允许它们分离,只要您关闭从A到B的级联。
是的,连接列不正确是正确的,我在将代码转换为伪代码时犯了一个错误。我同意移除级联,它是用来触发Genericontifier的;我同意正确的解决方案不应该基于需求使用它。我喜欢你将B加载到内存中的想法,我可能会用它作为解决方案。谢谢你的见解。谢谢,这是我之前实现的,但我想要一个隐式的解决方案,而不是显式地将B链接到A。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.");
}
}
}
@JoinColumn(name = "a_id")
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "b_id")