I´ve created a test case for this issue. It seems like flushing the session would solve the problem. Unfortunately I´ve created a generic save method for all my objects, so I just can´t flush after every deletion. Is there any other possibility to solve this problem? Sth. like a Flush-OnDelete-annotation for attributes? ;)
I think I´m not the only one, saving his objects not in concrete methods. Another solution would be a database that supports deferred constraint checks :(
I hope someone has at least a straw to catch.
Code:
/*
* Copyright 2014 JBoss Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hibernate.bugs;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
import org.hibernate.criterion.Restrictions;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
/**
* This template demonstrates how to develop a test case for Hibernate ORM,
* using its built-in unit test framework. Although ORMStandaloneTestCase is
* perfectly acceptable as a reproducer, usage of this class is much preferred.
* Since we nearly always include a regression test with bug fixes, providing
* your reproducer using this method simplifies the process.
*
* What's even better? Fork hibernate-orm itself, add your test case directly to
* a module's unit tests, then submit it as a PR!
*/
public class ORMUnitTestCase extends BaseCoreFunctionalTestCase {
// Add your entities here.
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Topic.class };
}
// If you use *.hbm.xml mappings, instead of annotations, add the mappings
// here.
@Override
protected String[] getMappings() {
return new String[] {
// "Foo.hbm.xml",
// "Bar.hbm.xml"
};
}
// If those mappings reside somewhere other than resources/org/hibernate/test,
// change this.
@Override
protected String getBaseForMappings() {
return "org/hibernate/test/";
}
// Add in any settings that are specific to your test. See
// resources/hibernate.properties for the defaults.
@Override
protected void configure(Configuration configuration) {
super.configure(configuration);
configuration.setProperty(AvailableSettings.SHOW_SQL, Boolean.TRUE.toString());
configuration.setProperty(AvailableSettings.FORMAT_SQL, Boolean.TRUE.toString());
// configuration.setProperty( AvailableSettings.GENERATE_STATISTICS,
// Boolean.TRUE.toString() );
}
/**
* Create 3 Topics and save them in different orders. 1/2/3 - 2/3/1 - 1/3/2 -
* 3/2/1
*
* @throws Exception
*/
@Test
public void createTopicsPerlmut() throws Exception {
Session s = openSession();
List<Topic> topicList = createTopics();
saveTopics(s, topicList);
topicList = createTopics();
topicList.add(topicList.get(0));
topicList.remove(0);
saveTopics(s, topicList);
topicList = createTopics();
topicList.add(topicList.get(1));
topicList.remove(1);
saveTopics(s, topicList);
// Reverse
topicList = createTopics();
Collections.reverse(topicList);
saveTopics(s, topicList);
s.close();
}
/**
* Create 3 Topics. Save them. Load them again in new session. Delete last one
* and therefore create a new one. save.
*
* @throws Exception
*/
@Test
public void createLoadAndChangeOrder() throws Exception {
Session s = openSession();
// Create and save 3 Topics
List<Topic> topicList = createTopics();
saveTopics(s, topicList);
// Get topic 2
Topic topic2 = topicList.get(1);
// Get topic 3
Topic topic3 = topicList.get(2);
// Ensure to read the topics from database
s.flush();
s = openSession();
Criteria crit = s.createCriteria(Topic.class.getName());
crit.add(Restrictions.eq("id", topic3.getId()));
@SuppressWarnings("unchecked")
List<Topic> loadList = crit.list();
Topic loadedTopic3 = loadList.get(0);
crit = s.createCriteria(Topic.class.getName());
crit.add(Restrictions.eq("id", topic2.getId()));
loadList = crit.list();
Topic loadedTopic2 = loadList.get(0);
Transaction t = s.beginTransaction();
// Delete Topic 3
s.delete(loadedTopic3);
// Is this flush replaceable with another construct?
s.flush();
// Create a new Topic for topic3, order after topic2 and save
Topic topic4 = new Topic("Topic 4");
topic4.setPredecessor(loadedTopic2);
s.saveOrUpdate(topic4);
t.commit();
}
/**
* Create 3 Topics, delete the last one, create a new one, then save all.
*
* @throws Exception
*/
@Test
public void createAndChangeOrder() throws Exception {
Session s = openSession();
List<Topic> topicList = createTopics();
saveTopics(s, topicList);
// Get last topic
Topic topic3 = topicList.get(2);
Transaction transaction = s.beginTransaction();
s.delete(topic3);
s.flush();
Topic topic4 = new Topic("Topic 4");
Topic topic2 = topicList.get(1);
topic4.setPredecessor(topic2);
s.saveOrUpdate(topic4);
transaction.commit();
}
/**
* Save Topics.
*
* @param s
* @param topicList
* @throws PersistentException
*/
private void saveTopics(Session s, List<Topic> topicList) throws Exception {
Transaction transaction = s.beginTransaction();
System.out.println("New transaction");
for (Topic topic : topicList) {
System.out.println(topic.getDescription());
s.saveOrUpdate(topic);
}
transaction.commit();
}
/**
* Create 3 Topics in a list with the order: 1,2,3.
*
* @return
*/
private List<Topic> createTopics() {
List<Topic> topicList = new ArrayList<>();
Topic topic1 = new Topic("Topic 1");
topicList.add(topic1);
Topic topic2 = new Topic("Topic 2");
topicList.add(topic2);
Topic topic3 = new Topic("Topic 3");
topicList.add(topic3);
topic2.setPredecessor(topic1);
topic3.setPredecessor(topic2);
return topicList;
}
}
Code:
package org.hibernate.bugs;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
@Entity
@org.hibernate.annotations.Proxy(lazy = false)
@Table(name = "TOPIC", schema = "PUBLIC")
public class Topic implements Serializable {
public Topic() {
}
@Column(name = "ID", nullable = false, unique = true, length = 10)
@Id
@GeneratedValue(generator = "TOPIC_ID_GENERATOR")
@org.hibernate.annotations.GenericGenerator(name = "TOPIC_ID_GENERATOR", strategy = "native")
private int id;
@Column(name = "DESCRIPTION", nullable = true, length = 255)
private String description;
@ManyToOne(targetEntity = Topic.class, fetch = FetchType.LAZY)
@JoinColumns({ @JoinColumn(name = "PREDECESSOR", referencedColumnName = "ID", unique = true) })
private Topic predecessor;
@ManyToOne(targetEntity = Topic.class, fetch = FetchType.LAZY)
@JoinColumns({ @JoinColumn(name = "PARENT", referencedColumnName = "ID") })
private Topic parent;
private void setId(int value) {
this.id = value;
}
public int getId() {
return id;
}
public int getORMID() {
return getId();
}
public void setDescription(String value) {
this.description = value;
}
public String getDescription() {
return description;
}
public void setPredecessor(Topic value) {
this.predecessor = value;
}
public Topic getPredecessor() {
return predecessor;
}
public void setParent(Topic value) {
this.parent = value;
}
public Topic getParent() {
return parent;
}
public Topic(String description) {
this.setDescription(description);
}
public String toString() {
return String.valueOf(getId());
}
}