持久化时引用没有外键的静态表

时间:2018-08-13 11:48:35

标签: java hibernate jpa

比方说,我有两个实体(省略了获取器/设置器):

@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.");
        }

    }
}

2 个答案:

答案 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的级联,是否允许它们分离都没关系。