Объяснить поведение при сопоставлении автоматически добавленной составной последовательности идентификаторов с Hibernate
У меня есть таблица
CREATE TABLE `SomeEntity` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`subid` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`,`subid`),
У меня есть класс сущности с полем автоматического увеличения в нем. Я хочу прочитать идентификатор автоматического увеличения, назначенный ему, когда он будет сохранен
Аннотации к геттеру приведены ниже
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 - возврат автоматически сгенерированного идентификатора после сохранения()
@Transactional
@Override
public void daoSaveEntity(SomeEntity entity) {
entityManager.persist(entity);
}
Теперь за пределами этой транзакции я пытаюсь получить идентификатор auto increment 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 и @Id
аннотации к субиду и `entityManager.persist 'в обновлении
Когда я работал с 300 потоками, некоторые с ошибкой подключения "слишком много соединений" Состояние базы данных
id 1 subid 0
id 1 subid 1
id 1 subid 2
.. ....
id 1 subid 150
субад всегда инкрементален, условие гонки - только то, что будет ACTIVE, undefined из-за состояния гонки
Случай 2 с @Id
в id и @Id
аннотации в субиде и `entityManager.merge 'в обновлении
id 1 субад 0 id 1 subid 0 id 2 subid 0 ...... id 151 subid 0 (Возможно, только совпадение, что еще один поток, чем случай 1, был успешным?)
Случай 3 С @GeneratedValue
и @Id
в id и ** NO @Id
аннотация по субиду и entityManager.persist в обновлении **
exception - Отдельный объект, переданный для сохранения
Случай 3 С @GeneratedValue
и @Id
в id и ** NO @Id
аннотация по субиду и 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 (Только один поток был бы успешным, но из-за обновленного состояния ранга от 0 до 2)
Случай 5 С @GeneratedValue
и @Id
на id и @Id
аннотации на субад
Create также терпит неудачу с subid org.hibernate.PropertyAccessException: IllegalArgumentException произошло при вызове setter of SomeEntity.id
Пожалуйста, объясните причины. Из javadoc методов я знаю, что
persist - сделать экземпляр управляемым и постоянным.
merge - объединить состояние данного объекта в текущий контекст сохранения.
Мой вопрос больше связан с тем, как hibernate управляет аннотациями.
Почему в случае 3 существует исключение отдельного объекта, если сеанс еще не закрыт?
Почему в случае 5 существует исключение IllegalArgumentException?
Я использую hibernate 3.6 mysql 5 и Spring 4
Также попробуйте указать такой инкрементный id и субад. (Использование пользовательского SelectGenerator с демонстрационной реализацией или любым другим способом без выполнения столбца concat)
Ответы
Ответ 1
Поскольку поле id
уже уникально и автоматически увеличивается, в этом случае вам не нужен составной идентификатор, чтобы ваш объект мог выглядеть так:
@Id
@Column(name = "id")
public long getId() {
return id;
}
@Column(name = "subid")
public int getSubid() {
return subid;
}
Объект может быть извлечен с помощью идентификатора с помощью диспетчера сущностей:
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 также имеет SelectGenerator, который может извлекать идентификатор из столбца базы данных, что полезно, когда база данных генерирует идентификатор, используя вызывать.
К сожалению, он не работает с составными идентификаторами, поэтому вы завялили свой собственный расширенный SelectGenerator
или использовали столбец id_sub_id
, который объединяет id и sub-id в один столбец VARCHAR:
'1-0'
'1-1'
'2-0'
'2-1'
Вам нужно написать триггер базы данных, чтобы обновить два столбца, используя хранимую процедуру, специфичную для базы данных, и объединить два столбца в VARCHAR. Затем вы сопоставляете агрегированный столбец с помощью стандартного 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;
}
Ответ 2
Скажем, у меня есть несколько книг с идентификатором и версией. Идентификатор принадлежит книге который может иметь много обновленных версий, причем последний из них является текущим которая будет в основном запрашиваться. Что было бы лучше подход? Должен ли я использовать отображение "один ко многим"
См. схему дизайна ниже, чтобы понять, как ее правильно отобразить:
![enter image description here]()
Сохранение книги:
@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);
}
Отображение схемы таблицы логической модели выше:
![enter image description here]()
Ответ 3
Просто потому, что никто не упоминал об этом.
У меня есть таблица
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`)
);
Атрибутом версии может быть int, short, long или timestamp. Он увеличивается, когда транзакция успешно совершает. Это приводит к операции SQL, например:
UPDATE User SET ..., version = version + 1
WHERE id = ? AND version = readVersion
источник: blogs.oracle.com (выделение)
Я немного изменил пример. Я рекомендую использовать типы полей Integer
, Long
и т.д. Для полей базы данных. Даже id
, который равен NOT NULL
, будет иметь значение null, прежде чем вы сохраните объект в первый раз. Я считаю, что использование типов ящиков делает его явным, что эти поля могут и будут null
.