我在Spring项目中使用JPA和Hibernate实现。 实体如下:
@Table(uniqueConstraints={@UniqueConstraint(columnNames = {"channel", "start", "end"})})
@Entity(name = "schedule")
public class Schedule {
@Id
@Column(name = "idSched", nullable = false)
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idSched;
@Column(name = "start", nullable = false)
private Timestamp start;
@Column(name = "end", nullable = false)
private Timestamp end;
@ManyToOne(targetEntity=Channel.class, fetch=FetchType.LAZY, cascade={CascadeType.ALL})
@JoinColumn(name="channel")
private Channel channel;
}
@Table(uniqueConstraints={@UniqueConstraint(columnNames = {"idChBusiness"})})
@Entity(name = "channel")
public class Channel {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idChPersistence;
@JsonProperty
@Column(name = "idChBusiness", nullable = false, length = 50)
private String idChBusiness;
@OneToMany(targetEntity=Schedule.class, mappedBy="channel", fetch=FetchType.LAZY, cascade={CascadeType.ALL}, orphanRemoval=true)
private List<Schedule> listSchedules;
}
然后我为每个实体都有一个存储库:
public interface ChannelRepository extends JpaRepository<Channel, Long>,
JpaSpecificationExecutor<Channel>, ChannelRepositoryCustom {
...
}
public interface ScheduleRepository extends JpaRepository<Schedule, Long>,
JpaSpecificationExecutor<Schedule> {
...
}
我正在使用JPA Repository的save()方法来插入我的实体。 我的主要目标是在单个save()查询中保存Schedule实体列表。
我认为计划在输入数据中是唯一的。也就是说,没有两个具有相同(通道,开始,结束)值的Schedules,无论如何,我正在使用Schedule实体的@UniqueConstraint阻止它。
但是,在我的输入数据中,有不同的Schedule实体引用具有相同idChBusiness的Channels。我不希望表中有两个具有相同idChBusiness的Channel实体,这就是我尝试使用Channel实体的@UniqueConstraint阻止的。
因此,我可以使用某个频道属性保存第一个日程表,并保存这两个实体。
问题在于,当我尝试保存包含与第一个Schedule实体具有相同idChBusiness的channel属性的第二个Schedule时。然后违反了idChBusiness @UniqueContraint,并且未保存Channel实体,但父节目实体也都没有。 具体来说,我得到的例外情况如下:
org.springframework.dao.InvalidDataAccessApiUsageException:已分离 实体传递给持久化:com.persistence.domain.Channel;嵌套 异常是org.hibernate.PersistentObjectException:分离的实体 传递给持久化:com.persistence.domain.Channel
但我认为在这种情况下,概念比具体错误更具相关性。
那么,是否有任何JPA模式可以使这个工作并能够使用单个save()查询保存列表计划实体?即使没有,任何其他建议将不胜感激。
谢谢!
更新1:
我尝试用@Jens Schauder方法解决它。这是代码:
public class DatabaseLoader() {
...
public void doLoad(List<Schedule> lSched) {
for (Schedule sched: lSched) {
Channel ch = chServ.findByIdChBusiness(sched.getChannel().getIdChBusiness());
if (ch != null) { // If a duplicate channel does exist -> Then remove it
chServ.deleteChannel(ch);
}
schedServ.insertSchedule(sched);
}
}
@Service
@Transactional
public class ChannelService {
...
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deleteChannel(Channel ch) {
chRep.delete(ch);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertChannel(Channel ch) {
chRep.save(ch);
}
}
@Service
@Transactional
public class ScheduleService {
...
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertSchedule(Schedule sched){
schedRep.save(sched);
}
}
这就是结果:
成功插入第一个时间表(及其频道)。接下来,我尝试插入第二个schedue,其channel属性与插入的第一个schedule的channel属性具有相同的idChBusiness。
然后满足if条件,所以我应该删除该频道,以避免在Channel表上出现 @UniqueConstraint 违规。当我这样做时,我刚插入的第一个计划也会被删除,因为 cascade = CascadeType.ALL 属性。
此外,在执行 schedServ.insertSchedule(sched)句子时,我得到以下PersistentObjectException:
org.hibernate.PersistentObjectException:传递给的分离实体 坚持:com.persistence.domain.Channel
我使用 CascadeType.PERSIST 代替 CascadeType.ALL 进行了尝试,结果相同。
如果我尝试删除CascadeType属性,则永远不会保存Channel对象,并且我得到以下异常:
org.hibernate.TransientPropertyValueException:对象引用一个 未保存的瞬态实例 - 保存之前的瞬态实例 flushing:com.persistence.domain.Schedule.channel - &gt; com.persistence.domain.Channel
我在这里做错了什么?
再次感谢你!
答案 0 :(得分:2)
您自己发现的唯一约束使交易无法成功完成。因此,您应该将其视为安全网。它可以防止数据库中的错误数据,但它根本无助于解决问题。
解决方案是首先检查是否存在重复,删除重复项,然后保存数据。
仅在(可能很少见)条件下,在检查重复项之后,在实际存储数据之前,引入了重复项,UniqueConstraint将阻止数据进入数据库。在大多数应用程序中,这种情况由一些通用的&#34; Oops充分处理,有些出错了。请再试一次&#34;异常处理程序。