使用Hibernate

时间:2015-07-11 21:38:14

标签: 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`),

我有一个带有自动增量字段的实体类。我想读取持久化时分配给它的自动增量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帖子中的解决方案 JPA - Returning an auto generated id after persist()

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

我从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为@Id,{sub} @Id注释和`entityManager.persist&#39;在更新中 当我使用300个线程运行时,一些连接异常失败&#34;连接太多&#34;数据库状态是

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

subid总是递增的,竞争条件只是因为竞争条件而未定义哪一个

案例2,其中id为@Id,{sub} @Id注释和`entityManager.merge&#39;在更新中

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

案例3 ID为@GeneratedValue@Id,而subid上的** NO @Id注释和更新中的entityManager.persist **  exception - 传递给persist的分离实体

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

id 1 subid 0

下次更新后

id 1 subid 1

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

id 1 subid 2

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

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

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

案例5 ID为@GeneratedValue@Id,而子ID为@Id注释
使用subid org.hibernate.PropertyAccessException创建也失败:调用SomeEntity.id的setter时发生IllegalArgumentException

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

persist - 使实例管理和持久化。

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

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

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

2 个答案:

答案 0 :(得分:3)

由于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); 

或者您可以使用同时采用{​​{1}}和id的查询来获取实体:

subid

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

不幸的是,它不适用于复合ID,因此您编写了自己的扩展MyEntity myEntity = entityManager.createTypeQuery("select me from MyEntity where id = :id and subid = :subid", MyEntity.class) .setParameter("id", entityId) .setParameter("subid", entitySubId) .getSingleResult(); 或使用将id和sub-id组合到单个VARCHAR列中的单个字符串SelectGenerator列:

id_sub_id

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

SelectGenerator

答案 1 :(得分:0)

因为没人提到这一点。

  

我有一张桌子

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

我有一个带有自动增量字段的实体类。

     

[..]

     

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

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

实际上有一个注释可以完全按照您的意愿完成,而无需编写任何代码:

@Version

  

版本化实体标有@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等框类型。在您第一次保存实体之前,即使idNOT NULL也将为空。我发现使用框类型可以明确表示这些字段可以是null