Java 持久化时引用不带外键的静态表

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,只保留唯一的原因

假设我有两个实体(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,只保留唯一的
原因

如果我在持久化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")