我试图从Hibernate3的角度,找出两个几乎相同的类集的行为不同的原因。总的来说,我对冬眠还比较陌生,我希望我遗漏了一些关于映射或时间问题的非常明显的东西,或者那些线路上的一些东西,但是我昨天花了一整天的时间盯着这两个集合,以及任何可能导致一个能够被坚持,另一个不能完全逃脱我的影响的差异。
我提前对这个问题的长度进行了评估,但这一切都取决于一些非常具体的实现细节。
我已经用注释映射了下面的类,并由Hibernate3管理。(如果具体的版本是相关的,我会弄清楚它是什么)。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 CollectionFelements,如下所示:
...
@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的目的是记录作业在一系列状态更改中移动的时间,这些状态更改在job state中以枚举的形式列出,这些枚举知道提升和降低规则。用于通过一系列状态推进作业的接口是用日期和用户调用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)
这就是我的问题所在……一个几乎相同的类集(事实上,是如此的相同以至于我一直在努力使它成为一个服务于两个业务实体的单个类)运行得非常好。除了名字外,它是相同的。不是工作而是网络。它不是jobstateChange,而是webstateChange。它不是Jobstate,而是WebState。Job和Web的SortedStateChanges集都映射为Hibernate CollectionFelements。两者都是@可嵌入的。两者都是SortType.Natural。两者都由枚举支持,其中包含一些高级规则。然而,当为Web运行一个几乎相同的测试时,没有发现任何问题,数据刷新也很好。为了简洁起见,我不会在这里包括所有的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));
}
...
}
如您所见,我断言Web是按照我预期的方式重新构建的,我推进它,将其刷新到数据库,然后重新获取它并验证状态是否如我预期的那样。一切正常。约伯不是这样。
一个可能相关的细节:如果我停止将jobstatechange.data映射为时间戳而不是日期,重构代码就可以正常工作,
和
确保所有状态更改始终在不同的日期发生。问题是,这个特定的业务实体可以在一天内经历许多状态更改,因此需要按时间戳而不是按日期排序。如果我不这样做,那么我就无法正确地对状态更改进行排序。这就是说,webstatechange.date也被映射为时间戳,因此我再次完全搞不清这个错误是从哪里产生的。
我试图做一个相当彻底的工作来提供实现的所有技术细节,但是由于这个特定的问题
非常
具体的实现,如果我遗漏了什么,请在评论中告诉我,我会把它包括进来。
非常感谢你的帮助!
更新
:因为它对解决我的问题很重要,所以我还必须包括WebStateChange类的相关部分。
...
@Embeddable
public class WebStateChange implements Comparable<WebStateChange> {
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable = false)
private Date date;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = WebState.FIELD_LENGTH)
private WebState state;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "acting_user_id", nullable = false)
private User actingUser;
...
WebStateChange(
final WebState state,
final User actingUser,
final Date date) {
ExceptionUtils.illegalNullArgs(state, actingUser, date);
this.state = state;
this.actingUser = actingUser;
this.date = new Date(date.getTime());
}
@Override
public int compareTo(final WebStateChange otherStateChange) {
return this.date.compareTo(otherStateChange.date);
}
@Override
public boolean equals(final Object candidate) {
if (this == candidate) {
return true;
} else if (!(candidate instanceof WebStateChange)) {
return false;
}
WebStateChange candidateWebState = (WebStateChange) candidate;
return this.getState() == candidateWebState.getState()
&& this.getUser().equals(candidateWebState.getUser())
&& this.getDate().equals(candidateWebState.getDate());
}
@Override
public int hashCode() {
return this.getState().hashCode()
+ this.getUser().hashCode()
+ this.getDate().hashCode();
}
...
}