HHH-6382: Allow to use @OnDelete annotation on unidirectional @OneToMany associations

This commit is contained in:
Alexey Nesterov 2017-08-12 10:17:39 +01:00 committed by Vlad Mihalcea
parent 1392b43852
commit 7c2a588613
3 changed files with 107 additions and 9 deletions

View File

@ -46,6 +46,7 @@
import org.hibernate.annotations.LazyGroup; import org.hibernate.annotations.LazyGroup;
import org.hibernate.annotations.Loader; import org.hibernate.annotations.Loader;
import org.hibernate.annotations.ManyToAny; import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OptimisticLock; import org.hibernate.annotations.OptimisticLock;
import org.hibernate.annotations.OrderBy; import org.hibernate.annotations.OrderBy;
import org.hibernate.annotations.Parameter; import org.hibernate.annotations.Parameter;
@ -480,6 +481,15 @@ public void bind() {
throw new AnnotationException( message ); throw new AnnotationException( message );
} }
if (!isMappedBy
&& oneToMany
&& property.isAnnotationPresent( OnDelete.class )
&& !property.isAnnotationPresent( JoinColumn.class )) {
String message = "Unidirectional one-to-many associations annotated with @OnDelete must define @JoinColumn: ";
message += StringHelper.qualify( propertyHolder.getPath(), propertyName );
throw new AnnotationException( message );
}
collection.setInverse( isMappedBy ); collection.setInverse( isMappedBy );
//many to many may need some second pass informations //many to many may need some second pass informations

View File

@ -293,12 +293,6 @@ public void validate(Mapping mapping) throws MappingException {
assert getKey() != null : "Collection key not bound : " + getRole(); assert getKey() != null : "Collection key not bound : " + getRole();
assert getElement() != null : "Collection element not bound : " + getRole(); assert getElement() != null : "Collection element not bound : " + getRole();
if ( getKey().isCascadeDeleteEnabled() && ( !isInverse() || !isOneToMany() ) ) {
throw new MappingException(
"only inverse one-to-many associations may use on-delete=\"cascade\": "
+ getRole()
);
}
if ( !getKey().isValid( mapping ) ) { if ( !getKey().isValid( mapping ) ) {
throw new MappingException( throw new MappingException(
"collection foreign key mapping has wrong number of columns: " "collection foreign key mapping has wrong number of columns: "

View File

@ -6,20 +6,32 @@
*/ */
package org.hibernate.test.annotations.onetomany; package org.hibernate.test.annotations.onetomany;
import javax.persistence.PersistenceException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.PersistenceException;
import org.hibernate.AnnotationException;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.Transaction; import org.hibernate.Transaction;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistry;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.hibernate.mapping.Column; import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.PersistentClass;
@ -36,6 +48,7 @@
import org.junit.Test; import org.junit.Test;
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping; import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
@ -345,6 +358,46 @@ public void testCascadeDelete() throws Exception {
s.close(); s.close();
} }
@Test
public void testCascadeDeleteWithUnidirectionalAssociation() throws Exception {
OnDeleteUnidirectionalOneToManyChild child = new OnDeleteUnidirectionalOneToManyChild();
doInHibernate( this::sessionFactory, session -> {
OnDeleteUnidirectionalOneToManyParent parent = new OnDeleteUnidirectionalOneToManyParent();
parent.children = Collections.singletonList( child);
session.persist( parent );
} );
doInHibernate( this::sessionFactory, session -> {
session.createQuery("delete from OnDeleteUnidirectionalOneToManyParent").executeUpdate();
} );
doInHibernate( this::sessionFactory, session -> {
OnDeleteUnidirectionalOneToManyChild e1 = session.get( OnDeleteUnidirectionalOneToManyChild.class, child.id );
assertNull( "delete cascade should work", e1 );
} );
}
@Test
public void testOnDeleteWithoutJoinColumn() throws Exception {
StandardServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
.build();
try {
new MetadataSources( serviceRegistry )
.addAnnotatedClass( OnDeleteUnidirectionalOneToMany.class )
.addAnnotatedClass( ParentUnawareChild.class )
.getMetadataBuilder()
.build();
}
catch ( AnnotationException e ) {
assertTrue(e.getMessage().contains( "Unidirectional one-to-many associations annotated with @OnDelete must define @JoinColumn" ));
}
finally {
StandardServiceRegistryBuilder.destroy( serviceRegistry );
}
}
@Test @Test
public void testSimpleOneToManySet() throws Exception { public void testSimpleOneToManySet() throws Exception {
Session s; Session s;
@ -503,7 +556,9 @@ protected Class[] getAnnotatedClasses() {
Person.class, Person.class,
Organisation.class, Organisation.class,
OrganisationUser.class, OrganisationUser.class,
Model.class Model.class,
OnDeleteUnidirectionalOneToManyParent.class,
OnDeleteUnidirectionalOneToManyChild.class
}; };
} }
@ -511,4 +566,43 @@ protected Class[] getAnnotatedClasses() {
protected String[] getXmlFiles() { protected String[] getXmlFiles() {
return new String[] { "org/hibernate/test/annotations/onetomany/orm.xml" }; return new String[] { "org/hibernate/test/annotations/onetomany/orm.xml" };
} }
@Entity(name = "OnDeleteUnidirectionalOneToManyParent")
public static class OnDeleteUnidirectionalOneToManyParent {
@Id
@GeneratedValue
Long id;
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "a_id")
@OnDelete(action = OnDeleteAction.CASCADE)
List<OnDeleteUnidirectionalOneToManyChild> children;
}
@Entity(name = "OnDeleteUnidirectionalOneToManyChild")
public static class OnDeleteUnidirectionalOneToManyChild {
@Id
@GeneratedValue
Long id;
}
@Entity(name = "OnDeleteUnidirectionalOneToMany")
public static class OnDeleteUnidirectionalOneToMany {
@Id
Long id;
@OneToMany(cascade = CascadeType.ALL)
@OnDelete(action = OnDeleteAction.CASCADE)
List<ParentUnawareChild> children;
}
@Entity(name = "ParentUnawareChild")
public static class ParentUnawareChild {
@Id
Long id;
}
} }