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
更新了问题。