[英]Explain behaviors in mapping auto incremented composite id sequence with Hibernate
我有一张桌子
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<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
on id和@Id
注释on subid和`entityManager.persist'更新当我运行300个线程时,一些失败,连接异常“太多连接”数据库状态是
id 1 subid 0
id 1 subid 1
id 1 subid 2
.. ....
id 1 subid 150
subid总是递增的,竞争条件只是由于竞争条件而未定义的一个
情况2,在id上有@Id
,在subid上有@Id
注释,在更新中有`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在更新**异常 - 分离的实体传递给持久化
情况3 @GeneratedValue
和@Id
在id和** NO @Id
注释在subid和entityManager.merge在更新**如果按顺序运行更新数据库状态是
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(只有一个线程会成功,但由于竞争条件更新了subid从0到2)
案例5使用@GeneratedValue
和@Id
对id和@Id
注释在subid上
使用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,使用演示实现或任何其他方式而不进行列连接)
由于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的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 ( 强调我的 )
我稍微修改了一下这个例子。 我建议您对数据库字段使用Integer
, Long
等框类型。 在第一次保存实体之前,即使是非NOT NULL
id
也将为null 。 我发现使用框类型可以明确表示这些字段可以并且将为null
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.