用Hibernate解释自动递增的合成id序列的行为 [英] Explain behaviors in mapping auto incremented composite id sequence with Hibernate

查看:132
本文介绍了用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的注释如下

 私人长ID; 
私人诠释subid;
@Id
@GeneratedValue ** //我如何解决这个问题,让多行具有相同的id和不同的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后
JPA - 在persist()返回一个自动生成的id()

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

现在在这个事务之外,我试图让自动递增ID分配



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

我从Web服务调用这个函数

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

更新方法如下



< pre $ @Transactional
public void updateReportJob(SomeEntity someEntity){

查询查询=
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);
尝试{
Thread.sleep(i); //仅用于模拟并发问题
} catch(InterruptedException e){
e.printStackTrace();
}
列表<整数> resList =
entityManager.createQuery(从SomeEntity WHERE id =:id选择max(subid))
.setParameter(id,jobId).getResultList();
//将旧的subid增加1
int subid = resList.get(0);
SomeEntity.setsubid(subid + 1);
SomeEntity.setState(ACTIVE);
// entityManager.merge(SomeEntity);
entityManager.persist(SomeEntity);

$ / code>

我从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 ++;

带有 @Id 上的id和 @Id 更新中的subid和`entityManager.persist'的注解
当我运行300个线程时,一些失败,数据库状态是

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

subid始终是递增的,竞争条件只是由于竞争条件而未定义哪一个将是ACTIVE

案例2 with With @id 对id和 @Id 对subid和`entityManager.merge'进行更新



id 1 subid 0
id 1 subid 0
id 2 subid 0
.. ....
id 151 subid 0(也许只是一个co - 发现比case 1多一个线程是成功的吗?)



案例3使用 @Gener atedValue @Id 关于id和** NO @Id 注释在subid和entityManager.persist中更新**
异常 - 分离的实体传递到持久化



案例3使用 @ GeneratedValue @Id 关于ID和** NO @Id 注释在subid和entityManager.merge中更新**
如果更新按顺序运行,数据库状态为

$ p $ id 1 subid 0



  id 1 subid 1 

每次更新后,一次只有一行)

  id 1 subid 2 

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

  org.hibernate.HibernateException:具有给定标识符的多行发现: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中会出现分离实体异常?
为什么在情况5中存在IllegalArgumentException?


我使用的是hibernate 3.6 mysql 5和Spring 4
另外请建议一种实现这种增量id和subid的方式(使用自定义的SelectGenerator,或任何其他方式,而不做列连接) 由于 id 字段已经是唯一的并且自动递增,在这种情况下,您不需要复合标识,因此您的实体可以如下所示:

  @Id 
@Column(name =id)
public long getId(){
return id;


@Column(name =subid)
public int getSubid(){
return subid;



$ b

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

  entityManager.find(MyEntity.class,entityId); 

或者你可以使用一个同时带有 id subid

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

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

不幸的是,它不能用于组合标识符,所以你可以编写自己的扩展 SelectGenerator 或者使用单个字符串 id_sub_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;
}


I have a table

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

I have a entity class with an auto increment field in it.I want to read auto increment id assigned to it when it gets persisted

Annotations on getter are as below

  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;
  }

I want to have entities as

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

subid is default 0 in database and i am incrementing it programmatically on updates to that row. I tried the solution as in this SO post JPA - Returning an auto generated id after persist()

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

Now outside this transaction I am trying to get the auto increment id assigned

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

I am calling this from a web service

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

The update method is as below

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

i send N concurrent updates from N threads for entity with Id 1 and few other properties as below

SomeEnity entity = new SomeEntity();

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

Case 1 with With @Id on id and @Id annotation on subid and `entityManager.persist' in update When i ran with 300 threads some failed with connection exception "too many connections" The databse state is

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

the subid is always incremental ,the race condition is only that which one will be ACTIVE is undefined because of race condition

Case 2 with With @Id on id and @Id annotation on subid and `entityManager.merge' in update

id 1 subid 0 id 1 subid 0 id 2 subid 0 .. .... id 151 subid 0 (Perhaps just a co-incidence that one more thread than case 1 was successful? )

Case 3 With @GeneratedValue and @Id on id and **NO @Id annotation on subid and entityManager.persist in update** exception -- Detached entity passed to persist

Case 3 With @GeneratedValue and @Id on id and **NO @Id annotation on subid and entityManager.merge in update** if update is run sequentially the database state is

id 1 subid 0

after next update

id 1 subid 1

after each update same row is updated (leading to only one row at a time)

id 1 subid 2

Case 4 same as Case 3 with Concurrent updates if run concurrently(with 300 threads) i get the below exception

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

database state is id 1 subid 2 (Only one thread would have been successful but because of race condition updated subid from 0 to 2 )

Case 5 With @GeneratedValue and @Id on id and @Id annotation on subid
Create also fails with subid org.hibernate.PropertyAccessException: IllegalArgumentException occurred while calling setter of SomeEntity.id

Please explain the causes.From the javadoc of methods i know that

persist - Make an instance managed and persistent.

merge - Merge the state of the given entity into the current persistence context.

My question is more towards how hibernate manages the annotations. Why is there be a detached entity exception in case 3 when the session is not closed yet? Why is there a IllegalArgumentException in case 5 ?

I am using hibernate 3.6 mysql 5 and Spring 4 Also please suggest a way achieve such incremental id and subid.(Using custom SelectGenerator ,with a demo implementation or any other way without doing a column concat)

解决方案

Since the id field is already unique and auto incremented, you don't need a composite id in this case so your entity can look like this:

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

@Column(name = "subid")
public int getSubid() {
    return subid;
}

The entity can be fetched by id using the entity manager:

entityManager.find(MyEntity.class, entityId); 

or you could fetch the entity using a query that takes both the id and the 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 also has a SelectGenerator that can fetch the id from a database column, which is useful when the database generates the id using a trigger.

Unfortunately, it doesn't work with composite ids, so you wither wrote your own extended SelectGenerator or use a single string id_sub_id column that combines the id and sub-id into a single VARCHAR column:

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

You have to write a database trigger to update the two columns using a database specific stored procedure and aggregate the two columns into the VARCHAR one. You then map the aggregated column using the standard SelectGenerator to a String field:

@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;
}

这篇关于用Hibernate解释自动递增的合成id序列的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆