Я пытаюсь выяснить, почему два почти идентичных набора классов ведут себя по-разному с точки зрения Hibernate 3. Я довольно новичок в Hibernate в целом, и я надеюсь, что мне не хватает чего-то достаточно очевидного в отображениях или проблемах синхронизации или чего-то подобного, но я провел весь вчерашний день, уставившись на два набора и любые различия, которые могли бы привести чтобы один был настойчив, а другой не избежал меня полностью.
Я заранее извиняюсь за длину этого вопроса, но все это связано с некоторыми довольно конкретными деталями реализации.
У меня есть следующий класс, сопоставленный с аннотациями и управляемый Hibernate 3.? (если конкретная конкретная версия окажется уместной, я выясню, что это). Java версия 1.6.
...
@Embeddable
public class JobStateChange implements Comparable<JobStateChange> {
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable = false)
private Date date;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = JobState.FIELD_LENGTH)
private JobState state;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "acting_user_id", nullable = false)
private User actingUser;
public JobStateChange() {
}
@Override
public int compareTo(final JobStateChange o) {
return this.date.compareTo(o.date);
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
} else if (!(obj instanceof JobStateChange)) {
return false;
}
JobStateChange candidate = (JobStateChange) obj;
return this.state == candidate.state
&& this.actingUser.equals(candidate.getUser())
&& this.date.equals(candidate.getDate());
}
@Override
public int hashCode() {
return this.state.hashCode()
+ this.actingUser.hashCode()
+ this.date.hashCode();
}
}
Он отображается как Hibernate CollectionOfElements в классе Job следующим образом:
...
@Entity
@Table(
name = "job",
uniqueConstraints = {
@UniqueConstraint(
columnNames = {
"agency", //Job Name
"payment_type", //Job Name
"payment_file", //Job Name
"date_of_payment",
"payment_control_number",
"truck_number"
})
})
public class Job implements Serializable {
private static final long serialVersionUID = -1131729422634638834L;
...
@org.hibernate.annotations.CollectionOfElements
@JoinTable(name = "job_state", joinColumns = @JoinColumn(name = "job_id"))
@Sort(type = SortType.NATURAL)
private final SortedSet<JobStateChange> stateChanges = new TreeSet<JobStateChange>();
...
public void advanceState(
final User actor,
final Date date) {
JobState nextState;
LOGGER.debug("Current state of {} is {}.", this, this.getCurrentState());
if (null == this.currentState) {
nextState = JobState.BEGINNING;
} else {
if (!this.isAdvanceable()) {
throw new IllegalAdvancementException(this.currentState.illegalAdvancementStateMessage);
}
if (this.currentState.isDivergent()) {
nextState = this.currentState.getNextState(this);
} else {
nextState = this.currentState.getNextState();
}
}
JobStateChange stateChange = new JobStateChange(nextState, actor, date);
this.setCurrentState(stateChange.getState());
this.stateChanges.add(stateChange);
LOGGER.debug("Advanced {} to {}", this, this.getCurrentState());
}
private void setCurrentState(final JobState jobState) {
this.currentState = jobState;
}
boolean isAdvanceable() {
return this.getCurrentState().isAdvanceable(this);
}
...
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof Job)) {
return false;
}
Job otherJob = (Job) obj;
return this.getName().equals(otherJob.getName())
&& this.getDateOfPayment().equals(otherJob.getDateOfPayment())
&& this.getPaymentControlNumber().equals(otherJob.getPaymentControlNumber())
&& this.getTruckNumber().equals(otherJob.getTruckNumber());
}
@Override
public int hashCode() {
return this.getName().hashCode()
+ this.getDateOfPayment().hashCode()
+ this.getPaymentControlNumber().hashCode()
+ this.getTruckNumber().hashCode();
}
...
}
Цель JobStateChange состоит в том, чтобы записывать, когда Задание проходит через серию Изменений Состояния, которые изложены в JobState, как перечисления, которые знают о правилах повышения и уменьшения. Интерфейс, используемый для продвижения заданий через ряд состояний, заключается в вызове Job.advanceState () с датой и пользователем. Если задание можно продвигать в соответствии с правилами, закодированными в перечислении, то в SortedSet добавляется новый StateChange, и все счастливы. Если нет, генерируется исключение IllegalAdvancementException.
DDL, который генерируется следующим образом:
...
drop table job;
drop table job_state;
...
create table job (
id bigint generated by default as identity,
current_state varchar(25),
date_of_payment date not null,
beginningCheckNumber varchar(8) not null,
item_count integer,
agency varchar(10) not null,
payment_file varchar(25) not null,
payment_type varchar(25) not null,
endingCheckNumber varchar(8) not null,
payment_control_number varchar(4) not null,
truck_number varchar(255) not null,
wrapping_system_type varchar(15) not null,
printer_id bigint,
primary key (id),
unique (agency, payment_type, payment_file, date_of_payment, payment_control_number, truck_number)
);
create table job_state (
job_id bigint not null,
acting_user_id bigint not null,
date timestamp not null,
state varchar(25) not null,
primary key (job_id, acting_user_id, date, state)
);
...
alter table job
add constraint FK19BBD12FB9D70
foreign key (printer_id)
references printer;
alter table job_state
add constraint FK57C2418FED1F0D21
foreign key (acting_user_id)
references app_user;
alter table job_state
add constraint FK57C2418FABE090B3
foreign key (job_id)
references job;
...
Перед выполнением тестов в базу данных добавляются следующие данные
...
insert into job (id, agency, payment_type, payment_file, payment_control_number, date_of_payment, beginningCheckNumber, endingCheckNumber, item_count, current_state, printer_id, wrapping_system_type, truck_number)
values (-3, 'RRB', 'Monthly', 'Monthly','4501','1998-12-01 08:31:16' , '00000001','00040000', 40000, 'UNASSIGNED', null, 'KERN', '02');
insert into job_state (job_id, acting_user_id, date, state)
values (-3, -1, '1998-11-30 08:31:17', 'UNASSIGNED');
...
После того, как схема базы данных будет автоматически сгенерирована и перестроена с помощью инструмента Hibernate.
Следующий тест работает нормально до вызова Session.flush ()
...
@ContextConfiguration(locations = { "/applicationContext-data.xml", "/applicationContext-service.xml" })
public class JobDaoIntegrationTest
extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private JobDao jobDao;
@Autowired
private SessionFactory sessionFactory;
@Autowired
private UserService userService;
@Autowired
private PrinterService printerService;
...
@Test
public void saveJob_JobAdvancedToAssigned_AllExpectedStateChanges() {
//Get an unassigned Job
Job job = this.jobDao.getJob(-3L);
assertEquals(JobState.UNASSIGNED, job.getCurrentState());
Date advancedToUnassigned = new GregorianCalendar(1998, 10, 30, 8, 31, 17).getTime();
assertEquals(advancedToUnassigned, job.getStateChange(JobState.UNASSIGNED).getDate());
//Satisfy advancement constraints and advance
job.setPrinter(this.printerService.getPrinter(-1L));
Date advancedToAssigned = new Date();
job.advanceState(
this.userService.getUserByUsername("admin"),
advancedToAssigned);
assertEquals(JobState.ASSIGNED, job.getCurrentState());
assertEquals(advancedToUnassigned, job.getStateChange(JobState.UNASSIGNED).getDate());
assertEquals(advancedToAssigned, job.getStateChange(JobState.ASSIGNED).getDate());
//Persist to DB
this.sessionFactory.getCurrentSession().flush();
...
}
...
}
Ошибка: SQLCODE = -803, SQLSTATE = 23505:
could not insert collection rows: [jaci.model.job.Job.stateChanges#-3]
org.hibernate.exception.ConstraintViolationException: could not insert collection rows: [jaci.model.job.Job.stateChanges#-3]
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:94)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1416)
at org.hibernate.action.CollectionUpdateAction.execute(CollectionUpdateAction.java:86)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:170)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at jaci.dao.JobDaoIntegrationTest.saveJob_JobAdvancedToAssigned_AllExpectedStateChanges(JobDaoIntegrationTest.java:98)
at org.springframework.test.context.junit4.SpringTestMethod.invoke(SpringTestMethod.java:160)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTestMethod(SpringMethodRoadie.java:233)
at org.springframework.test.context.junit4.SpringMethodRoadie$RunBeforesThenTestThenAfters.run(SpringMethodRoadie.java:333)
at org.springframework.test.context.junit4.SpringMethodRoadie.runWithRepetitions(SpringMethodRoadie.java:217)
at org.springframework.test.context.junit4.SpringMethodRoadie.runTest(SpringMethodRoadie.java:197)
at org.springframework.test.context.junit4.SpringMethodRoadie.run(SpringMethodRoadie.java:143)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.invokeTestMethod(SpringJUnit4ClassRunner.java:160)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:97)
Caused by: com.ibm.db2.jcc.b.lm: DB2 SQL Error: SQLCODE=-803, SQLSTATE=23505, SQLERRMC=1;ACI_APP.JOB_STATE, DRIVER=3.50.152
at com.ibm.db2.jcc.b.wc.a(wc.java:575)
at com.ibm.db2.jcc.b.wc.a(wc.java:57)
at com.ibm.db2.jcc.b.wc.a(wc.java:126)
at com.ibm.db2.jcc.b.tk.b(tk.java:1593)
at com.ibm.db2.jcc.b.tk.c(tk.java:1576)
at com.ibm.db2.jcc.t4.db.k(db.java:353)
at com.ibm.db2.jcc.t4.db.a(db.java:59)
at com.ibm.db2.jcc.t4.t.a(t.java:50)
at com.ibm.db2.jcc.t4.tb.b(tb.java:200)
at com.ibm.db2.jcc.b.uk.Gb(uk.java:2355)
at com.ibm.db2.jcc.b.uk.e(uk.java:3129)
at com.ibm.db2.jcc.b.uk.zb(uk.java:568)
at com.ibm.db2.jcc.b.uk.executeUpdate(uk.java:551)
at org.hibernate.jdbc.NonBatchingBatcher.addToBatch(NonBatchingBatcher.java:46)
at org.hibernate.persister.collection.AbstractCollectionPersister.insertRows(AbstractCollectionPersister.java:1389)
В этом и заключается моя проблема & hellip; Практически идентичный набор классов (на самом деле, настолько идентичный, что я немного ломал голову, чтобы сделать его единым классом, обслуживающим обе бизнес-сущности) работает абсолютно нормально. Это идентично, кроме имени. Вместо работы это веб. Вместо JobStateChange это WebStateChange. Вместо JobState это WebState. SortedSet StateChanges для Job и Web отображаются как Hibernate CollectionOfElements. Оба @Embeddable. Оба SortType.Natural. Оба поддерживаются Перечислением с некоторыми правилами продвижения. И все же, когда для Web выполняется почти идентичный тест, проблема не обнаруживается, и данные сбрасываются нормально. Для краткости я не буду включать здесь все веб-классы, но я включу тест и, если кто-то захочет увидеть фактические источники, я включу их (просто оставьте комментарий).
Семя данных:
insert into web (id, stock_type, pallet, pallet_id, date_received, first_icn, last_icn, shipment_id, current_state)
values (-1, 'PF', '0011', 'A', '2008-12-31 08:30:02', '000000001', '000080000', -1, 'UNSTAGED');
insert into web_state (web_id, date, state, acting_user_id)
values (-1, '2008-12-31 08:30:03', 'UNSTAGED', -1);
Тест:
...
@ContextConfiguration(locations = { "/applicationContext-data.xml", "/applicationContext-service.xml" })
public class WebDaoIntegrationTest
extends AbstractTransactionalJUnit4SpringContextTests {
@Autowired
private WebDao webDao;
@Autowired
private UserService userService;
@Autowired
private SessionFactory sessionFactory;
...
@Test
public void saveWeb_WebAdvancedToNewState_AllExpectedStateChanges() {
Web web = this.webDao.getWeb(-1L);
Date advancedToUnstaged = new GregorianCalendar(2008, 11, 31, 8, 30, 3).getTime();
assertEquals(WebState.UNSTAGED, web.getCurrentState());
assertEquals(advancedToUnstaged, web.getState(WebState.UNSTAGED).getDate());
Date advancedToStaged = new Date();
web.advanceState(
this.userService.getUserByUsername("admin"),
advancedToStaged);
this.sessionFactory.getCurrentSession().flush();
web = this.webDao.getWeb(web.getId());
assertEquals(
"Web should have moved to STAGED State.",
WebState.STAGED,
web.getCurrentState());
assertEquals(advancedToUnstaged, web.getState(WebState.UNSTAGED).getDate());
assertEquals(advancedToStaged, web.getState(WebState.STAGED).getDate());
assertNotNull(web.getState(WebState.UNSTAGED));
assertNotNull(web.getState(WebState.STAGED));
}
...
}
Как вы можете видеть, я утверждаю, что сеть была восстановлена так, как я ожидаю, я продвигаю ее, сбрасываю в БД, а затем повторно получаю и проверяю, что состояния соответствуют ожидаемым. Все работает отлично. Не так с Иовом.
Возможно, важная деталь: код восстановления работает нормально, если я перестаю отображать JobStateChange.data как TIMESTAMP и вместо этого как DATE, и гарантируют, что все StateChanges всегда происходят в разные даты. Проблема заключается в том, что данный конкретный бизнес-объект может проходить через много изменений состояния в течение одного дня, и поэтому его нужно отсортировать по отметке времени, а не по дате. Если я этого не сделаю, я не могу правильно отсортировать StateChanges. При этом WebStateChange.date также отображается как TIMESTAMP, и поэтому я снова остаюсь совершенно сбитым с толку относительно того, откуда возникает эта ошибка.
Я пытался проделать довольно тщательную работу, предоставив все технические детали реализации, но поскольку этот конкретный вопрос очень специфичен для реализации, если я что-то пропустил, просто дайте мне знать в комментариях, и я включу.
Большое спасибо за вашу помощь!
ОБНОВЛЕНИЕ : Поскольку это оказывается важным для решения моей проблемы, я должен также включить соответствующие биты класса WebStateChange.