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

Java 解释使用Hibernate映射自动递增的复合id序列时的行为,java,mysql,spring,hibernate,jpa,Java,Mysql,Spring,Hibernate,Jpa,我有一张桌子 CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`,`subid`), CREATE TABLE `SomeEntity` ( `id` int(11) NOT NULL AUTO_INCREMENT, `subid` int(11) NOT NULL DEFA

我有一张桌子

CREATE TABLE `SomeEntity` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `subid` int(11) NOT NULL DEFAULT '0',
     PRIMARY KEY (`id`,`subid`),
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,我将在更新该行时以编程方式递增它。 我在这篇文章中尝试了解决方案

现在在这个事务之外,我正在尝试获得分配的自动增量id

      @Override
      public long serviceSaveEntity(SomeEntity entity) {
        dao.daoSaveEntity(entity);
        return entity.getId();
      }
我从一个web服务调用这个

  @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<Integer> 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);
  }
案例1,Id上有
@Id
,子Id上有
@Id
注释,更新中有“entityManager.persist”
当我使用300个线程运行时,一些线程失败,连接异常为“太多连接”,数据库状态为

   id 1 subid 0 
   id 1 subid 1
   id 1 subid 2
   ..  ....
   id 1 subid 150
id 1 subid 0
子ID始终是递增的,竞争条件仅是由于竞争条件而未定义的将处于活动状态的子ID

案例2,Id上有
@Id
,子Id上有
@Id
注释,更新中有“entityManager.merge”

id 1子id 0 id 1子id 0 id 2子id 0 .. .... ID151子ID0(可能只是比案例1多了一个线程成功的共同事件?)

案例3,Id上有
@GeneratedValue
@Id
,子Id和entityManager上有**NO
@Id
注释。持续更新** 异常--传递给持久化的分离实体

案例3,Id上有
@GeneratedValue
@Id
,子Id上有**NO
@Id
注释,更新中有entityManager.merge** 如果按顺序运行更新,则数据库状态为

   id 1 subid 0 
   id 1 subid 1
   id 1 subid 2
   ..  ....
   id 1 subid 150
id 1 subid 0
下次更新后

id 1 subid 1
每次更新后,更新同一行(一次只更新一行)

案例4与案例3相同,同时更新 如果并发运行(300个线程),我会得到以下异常

 org.hibernate.HibernateException: More than one row with the given identifier was found: 1
数据库状态为 id 1子id 2(只有一个线程会成功,但由于竞争条件,将子id从0更新为2)

案例5,Id上有
@GeneratedValue
@Id
,子Id上有
@Id
注释

创建子id为org.hibernate.PropertyAccessException时也失败:调用SomeEntity.id的setter时发生IllegalArgumentException

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

persist—使实例成为受管理的持久实例

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

我的问题更多的是关于hibernate如何管理注释。 会话尚未关闭时,案例3中为什么会出现分离实体异常? 为什么案例5中存在非法辩论例外?

我正在使用Hibernate3.6MySQL5和Spring4
另外,请建议一种实现这种增量id和子id的方法。(使用自定义SelectGenerator,使用演示实现或任何其他不使用列concat的方法)

由于
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); 
或者,您可以使用同时获取
id
subid
的查询来获取实体:

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的控件,当数据库使用触发器生成id时,该控件非常有用

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

'1-0'
'1-1'
'2-0'
'2-1' 
您必须编写一个数据库触发器,以使用特定于数据库的存储过程更新这两列,并将这两列聚合为VARCHAR一列。然后使用标准的
SelectGenerator
将聚合列映射到字符串字段:

@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);
}
检索最新图书版本:

@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`),
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
资料来源:(强调矿山)

我稍微修改了这个例子。我建议您对数据库字段使用框类型
Integer
Long
等。即使是
id
(即
notnull
)在首次保存实体之前也将为NULL。我发现使用框类型可以明确这些字段可以并且将是
null

更新了问题。