解释使用Hibernate映射自动递增的复合id序列的行为

我有一张桌子

CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`,`subid`), 

我有一个实体类,其中包含一个自动增量字段。我希望在持久化时读取分配给它的自动增量ID

getter上的注释如下

  private long id; private int subid; @Id @GeneratedValue **//How do i correct this to have multiple rows with same id and different subid** @Column(name = "id") public long getId() { return id; } @Id @Column(name = "subid") public int getSubid() { return subid; } 

我希望有实体

 id 1 subid 0 id 1 subid 1 id 1 subid 2 id 2 subid 0 

subid在数据库中默认为0,我在对该行的更新时以编程方式递增它。 我尝试了这个解决方案,就像这个SO post JPA – 在persist()之后返回一个自动生成的id

  @Transactional @Override public void daoSaveEntity(SomeEntity entity) { entityManager.persist(entity); } 

现在在此事务之外,我正在尝试分配自动增量ID

  @Override public long serviceSaveEntity(SomeEntity entity) { dao.daoSaveEntity(entity); return entity.getId(); } 

我是通过网络服务来调用它的

  @POST @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public Response createEntity(SomeEntity entity) { 

更新方法如下

  @Transactional public void updateReportJob(SomeEntity someEntity) { Query query = entityManager .createQuery("UPDATE SomeEntity SET state=:newState WHERE id = :id"); query.setParameter("newState","PASSIVE"); query.setParameter("id", id); query.executeUpdate(); double rand = Math.random(); int i = (int) (rand * 300); try { Thread.sleep(i); //only to simulate concurrency issues } catch (InterruptedException e) { e.printStackTrace(); } List resList = entityManager.createQuery("select max(subid) from SomeEntity WHERE id = :id") .setParameter("id", jobId).getResultList(); // Increment old subid by 1 int subid = resList.get(0); SomeEntity.setsubid(subid + 1); SomeEntity.setState("ACTIVE"); // entityManager.merge(SomeEntity); entityManager.persist(SomeEntity); } 

我发送来自N个线程的N个并发更新,用于具有Id 1的实体和少数其他属性,如下所示

SomeEnity entity = new SomeEntity();

  entity.setId(1); long num = Thread.currentThread().getId(); entity.setFieldOne("FieldOne" + num); entity.setFieldTwo("" + i); entity.setFieldThree("" + j); i++; j++; 

情况1带有@Id on id和@Id注释on subid和`entityManager.persist’更新当我运行300个线程时,一些失败,连接exception“太多连接”数据库状态是

  id 1 subid 0 id 1 subid 1 id 1 subid 2 .. .... id 1 subid 150 

subid总是递增的,竞争条件只是由于竞争条件而未定义的一个

情况2,在@Id上有@Id ,在subid上有@Id注释,在update中有`entityManager.merge’

id 1 subid 0 id 1 subid 0 id 2 subid 0 .. …. id 151 subid 0(也许只是一个共同发生,比案例1多一个线程成功?)

情况3 @GeneratedValue@Id在id和** NO @Id注释在subid和entityManager.persist在更新**exception – 分离的实体传递给持久化

情况3 @GeneratedValue@Id在id和** NO @Id注释在subid和entityManager.merge在更新**如果按顺序运行更新数据库状态是

 id 1 subid 0 

下次更新后

 id 1 subid 1 

每次更新后相同的行都会更新(一次只能导致一行)

 id 1 subid 2 

案例4与案例3相同,如果并发运行并发生并发更新 (使用300个线程),我得到以下exception

  org.hibernate.HibernateException: More than one row with the given identifier was found: 1 

数据库状态是id 1 subid 2(只有一个线程可以成功,但由于竞争条件更新了subid从0到2)

案例5使用@GeneratedValue@Id对id和@Id注释进行subid
使用subid org.hibernate.PropertyAccessException创建也失败:调用SomeEntity.id的setter时发生IllegalArgumentException

请解释原因。从我知道的方法的javadoc

持久化 – 使实例管理和持久化。

merge – 将给定实体的状态合并到当前持久性上下文中。

我的问题更多的是关于hibernate如何管理注释。 为什么在会话未关闭的情况下,案例3中存在分离的实体exception? 为什么在案例5中存在IllegalArgumentException?

我正在使用hibernate 3.6 mysql 5和Spring 4另外请建议一种方法来实现这样的增量id和subid。(使用自定义SelectGenerator,使用演示实现或任何其他方式而不进行列连接)

由于id字段已经是唯一且自动递增,因此在这种情况下您不需要复合ID,因此您的实体可能如下所示:

 @Id @Column(name = "id") public long getId() { return id; } @Column(name = "subid") public int getSubid() { return subid; } 

可以使用实体管理器通过id获取实体:

 entityManager.find(MyEntity.class, entityId); 

或者您可以使用同时包含idsubid的查询来获取实体:

 MyEntity myEntity = entityManager.createTypeQuery("select me from MyEntity where id = :id and subid = :subid", MyEntity.class) .setParameter("id", entityId) .setParameter("subid", entitySubId) .getSingleResult(); 

Hibernate还有一个可以从数据库列中获取id的SelectGenerator ,这在数据库使用触发器生成id时很有用。

不幸的是,它不适用于复合ID,因此您编写了自己的扩展SelectGenerator或使用单个字符串id_sub_id列将id和sub-id组合到单个VARCHAR列中:

 '1-0' '1-1' '2-0' '2-1' 

您必须编写数据库触发器以使用特定于数据库的存储过程更新两列,并将这两列聚合到VARCHAR中。 然后,使用标准SelectGenerator将聚合列SelectGenerator到String字段:

 @Id @Column(name = "id_sub_id") @GeneratedValue( strategy = "trigger" ) @GenericGenerator( name="trigger", strategy="org.hibernate.id.SelectGenerator", parameters = { @Parameter( name="keys", value="id_sub_id" ) } ) public String getId() { return id; } 

假设我有一些ID和版本的书。一个ID属于一本书,它可以有许多更新的版本,最新的版本是当前版本,这将是大多数将被查询的版本。 什么是更好的方法? 我应该使用一对多映射

请参阅下面的设计大纲,了解如何正确映射它:

在此处输入图像描述

保存书:

 @Transactional public void saveBook(Book book) { em.persist(book); } 

保存书籍版本:

 @Transactional public void saveBookVersion(BookVersion bookVersion) { Book book = em.find(Book.class, bookVersion.getBook().getId()); bookVersion.setBook(book); book.setLatestBookVersion(bookVersion); em.persist(bookVersion); } 

检索最新的书籍版本:

 @Transactional(readOnly=true) public Book getLatestBookVersion(Long bookId) { // it is enough if lastestBookVersion is loaded eagerly return em.find(Book.class, bookId); } 

上面逻辑模型的表模式映射:

在此处输入图像描述

只是因为没有人提到这一点。

我有一张桌子

 CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`,`subid`), 

我有一个实体类,其中包含一个自动增量字段。

[..]

subid在数据库中默认为0,我在对该行的更新时以编程方式递增它。

对我而言,这听起来很像版本控制。

实际上有一个注释可以完全按照您的需要进行操作,而无需编写任何代码:

@版

使用@Version注释标记版本化实体,如以下代码段所示:

 public class User { @ID Integer id; @Version Integer version; } 

及其相应的数据库模式具有版本列,例如由以下SQL语句创建的版本列:

 CREATE TABLE `User` ( `id` NUMBER NOT NULL, `version` NUMBER, PRIMARY KEY (`id`) ); 

version属性可以是int,short,long或timestamp。 当事务成功提交时,它会递增 。 这会导致SQL操作,如下所示:

 UPDATE User SET ..., version = version + 1 WHERE id = ? AND version = readVersion 

来源 : blogs.oracle.com 强调我的

我稍微修改了一下这个例子。 我建议您对数据库字段使用IntegerLong等框类型。 在第一次保存实体之前,即使是非NOT NULL id将为null 。 我发现使用框类型可以明确表示这些字段可以并且将为null