HHH-13060 - Throw an exception when two entities are declared with the same name

This commit is contained in:
Vlad Mihalcea 2018-10-25 11:02:33 +03:00
parent 45083813bd
commit 817e463764
11 changed files with 408 additions and 210 deletions

View File

@ -113,12 +113,34 @@ Hibernate offers multiple identifier generation strategies, see the <<chapters/d
[[entity-pojo-mapping]]
==== Mapping the entity
The main piece in mapping the entity is the `javax.persistence.Entity` annotation.
The `@Entity` annotation defines just one attribute `name` which is used to give a specific entity name for use in JPQL queries.
By default, the entity name represents the unqualified name of the entity class itself.
The main piece in mapping the entity is the http://docs.oracle.com/javaee/7/api/javax/persistence/Entity.html[`javax.persistence.Entity`] annotation.
The `@Entity` annotation defines just the https://docs.oracle.com/javaee/7/api/javax/persistence/Entity.html#name--[`name`] attribute which is used to give a specific entity name for use in JPQL queries.
By default, if the name attribute the `@Entity` annotation is missing, the unqualified name of the entity class itself will be used as the entity name
[IMPORTANT]
====
Because the entity name is given by the unqualified name of the class, Hibernate does not allow registering multiple entities with the same name even if the entity classes reside in different packages.
Without imposing this restriction, Hibernate would not know which entity class is referenced in a JPQL query if the unqualified entity name is associated with more then one entity classes.
====
In the following example, the entity name (e.g. `Book`) is given by the unqualified name of the entity class name.
[[entity-pojo-mapping-implicit-name-example]]
.`@Entity` mapping with an implicit name
====
[source,java]
----
include::{sourcedir-mapping}/identifier/Book.java[tag=entity-pojo-mapping-implicit-name-example, indent=0]
----
====
However, the entity name can also be set explicitly as illustrated by the following example.
[[entity-pojo-mapping-example]]
.Simple `@Entity` mapping
.`@Entity` mapping with an explicit name
====
[source,java]
----

View File

@ -1028,7 +1028,12 @@ This basic query allows retrieving entity names and corresponding Java classes c
====
[source, JAVA, indent=0]
----
include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-queries-example]
include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-queries-example1]
----
[source, JAVA, indent=0]
----
include::{sourcedir}/EntityTypeChangeAuditTest.java[tags=envers-tracking-modified-entities-queries-example2]
----
====

View File

@ -45,212 +45,215 @@
*/
public class EntityTypeChangeAuditTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Customer.class,
CustomTrackingRevisionEntity.class
};
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Customer.class,
CustomTrackingRevisionEntity.class
};
}
@Test
public void test() {
@Test
public void test() {
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = new Customer();
customer.setId( 1L );
customer.setFirstName( "John" );
customer.setLastName( "Doe" );
doInJPA( this::entityManagerFactory, entityManager -> {
Customer customer = new Customer();
customer.setId( 1L );
customer.setFirstName( "John" );
customer.setLastName( "Doe" );
entityManager.persist( customer );
} );
entityManager.persist( customer );
} );
EntityManagerFactory entityManagerFactory = null;
try {
Map settings = buildSettings();
settings.put(
org.hibernate.jpa.AvailableSettings.LOADED_CLASSES,
Arrays.asList(
ApplicationCustomer.class,
Customer.class,
CustomTrackingRevisionEntity.class
)
);
settings.put(
AvailableSettings.HBM2DDL_AUTO,
"update"
);
entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder(
new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ),
settings
).build().unwrap( SessionFactoryImplementor.class );
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::envers-tracking-modified-entities-queries-example1[]
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 1 )
.iterator().next()
.getFirst()
);
//end::envers-tracking-modified-entities-queries-example1[]
} );
final EntityManagerFactory emf = entityManagerFactory;
EntityManagerFactory entityManagerFactory = null;
try {
Map settings = buildSettings();
settings.put(
org.hibernate.jpa.AvailableSettings.LOADED_CLASSES,
Arrays.asList(
ApplicationCustomer.class,
CustomTrackingRevisionEntity.class
)
);
settings.put(
AvailableSettings.HBM2DDL_AUTO,
"update"
);
entityManagerFactory = Bootstrap.getEntityManagerFactoryBuilder(
new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ),
settings
).build().unwrap( SessionFactoryImplementor.class );
doInJPA( () -> emf, entityManager -> {
ApplicationCustomer customer = entityManager.find( ApplicationCustomer.class, 1L );
customer.setLastName( "Doe Jr." );
} );
final EntityManagerFactory emf = entityManagerFactory;
doInJPA( () -> emf, entityManager -> {
//tag::envers-tracking-modified-entities-queries-example[]
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$Customer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 1 )
.iterator().next()
.getFirst()
);
doInJPA( () -> emf, entityManager -> {
ApplicationCustomer customer = entityManager.find( ApplicationCustomer.class, 1L );
customer.setLastName( "Doe Jr." );
} );
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$ApplicationCustomer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 2 )
.iterator().next()
.getFirst()
);
//end::envers-tracking-modified-entities-queries-example[]
} );
}
finally {
if ( entityManagerFactory != null ) {
entityManagerFactory.close();
}
}
}
doInJPA( () -> emf, entityManager -> {
//tag::envers-tracking-modified-entities-queries-example2[]
assertEquals(
"org.hibernate.userguide.envers.EntityTypeChangeAuditTest$ApplicationCustomer",
AuditReaderFactory
.get( entityManager )
.getCrossTypeRevisionChangesReader()
.findEntityTypes( 2 )
.iterator().next()
.getFirst()
);
//end::envers-tracking-modified-entities-queries-example2[]
} );
}
finally {
if ( entityManagerFactory != null ) {
entityManagerFactory.close();
}
}
}
//tag::envers-tracking-modified-entities-revchanges-before-rename-example[]
@Audited
@Entity(name = "Customer")
public static class Customer {
//tag::envers-tracking-modified-entities-revchanges-before-rename-example[]
@Audited
@Entity(name = "Customer")
public static class Customer {
@Id
private Long id;
@Id
private Long id;
private String firstName;
private String firstName;
private String lastName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-before-rename-example[]
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-before-rename-example[]
public Long getId() {
return id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getCreatedOn() {
return createdOn;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
//tag::envers-tracking-modified-entities-revchanges-before-rename-example[]
}
//end::envers-tracking-modified-entities-revchanges-before-rename-example[]
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
//tag::envers-tracking-modified-entities-revchanges-before-rename-example[]
}
//end::envers-tracking-modified-entities-revchanges-before-rename-example[]
//tag::envers-tracking-modified-entities-revchanges-after-rename-example[]
@Audited
@Entity(name = "Customer")
public static class ApplicationCustomer {
//tag::envers-tracking-modified-entities-revchanges-after-rename-example[]
@Audited
@Entity(name = "Customer")
public static class ApplicationCustomer {
@Id
private Long id;
@Id
private Long id;
private String firstName;
private String firstName;
private String lastName;
private String lastName;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
@Temporal( TemporalType.TIMESTAMP )
@Column(name = "created_on")
@CreationTimestamp
private Date createdOn;
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-after-rename-example[]
//Getters and setters are omitted for brevity
//end::envers-tracking-modified-entities-revchanges-after-rename-example[]
public Long getId() {
return id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public void setId(Long id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Date getCreatedOn() {
return createdOn;
}
public Date getCreatedOn() {
return createdOn;
}
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
//tag::envers-tracking-modified-entities-revchanges-after-rename-example[]
}
//end::envers-tracking-modified-entities-revchanges-after-rename-example[]
public void setCreatedOn(Date createdOn) {
this.createdOn = createdOn;
}
//tag::envers-tracking-modified-entities-revchanges-after-rename-example[]
}
//end::envers-tracking-modified-entities-revchanges-after-rename-example[]
//tag::envers-tracking-modified-entities-revchanges-example[]
@Entity(name = "CustomTrackingRevisionEntity")
@Table(name = "TRACKING_REV_INFO")
@RevisionEntity
public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity {
//tag::envers-tracking-modified-entities-revchanges-example[]
@Entity(name = "CustomTrackingRevisionEntity")
@Table(name = "TRACKING_REV_INFO")
@RevisionEntity
public static class CustomTrackingRevisionEntity extends DefaultRevisionEntity {
@ElementCollection
@JoinTable(
name = "REVCHANGES",
joinColumns = @JoinColumn( name = "REV" )
)
@Column( name = "ENTITYNAME" )
@ModifiedEntityNames
private Set<String> modifiedEntityNames = new HashSet<>();
@ElementCollection
@JoinTable(
name = "REVCHANGES",
joinColumns = @JoinColumn( name = "REV" )
)
@Column( name = "ENTITYNAME" )
@ModifiedEntityNames
private Set<String> modifiedEntityNames = new HashSet<>();
public Set<String> getModifiedEntityNames() {
return modifiedEntityNames;
}
}
//end::envers-tracking-modified-entities-revchanges-example[]
public Set<String> getModifiedEntityNames() {
return modifiedEntityNames;
}
}
//end::envers-tracking-modified-entities-revchanges-example[]
}

View File

@ -0,0 +1,56 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.userguide.mapping.identifier;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
/**
* @author Vlad Mihalcea
*/
//tag::entity-pojo-mapping-implicit-name-example[]
@Entity
public class Book {
@Id
private Long id;
private String title;
private String author;
//Getters and setters are omitted for brevity
//end::entity-pojo-mapping-implicit-name-example[]
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
//tag::entity-pojo-mapping-implicit-name-example[]
}
//end::entity-pojo-mapping-implicit-name-example[]

View File

@ -289,9 +289,30 @@ public PersistentClass getEntityBinding(String entityName) {
@Override
public void addEntityBinding(PersistentClass persistentClass) throws DuplicateMappingException {
final String entityName = persistentClass.getEntityName();
final String jpaEntityName = persistentClass.getJpaEntityName();
if ( entityBindingMap.containsKey( entityName ) ) {
throw new DuplicateMappingException( DuplicateMappingException.Type.ENTITY, entityName );
}
PersistentClass matchingPersistentClass = entityBindingMap.values()
.stream()
.filter( existingPersistentClass -> existingPersistentClass.getJpaEntityName().equals( jpaEntityName ) )
.findFirst()
.orElse( null );
if ( matchingPersistentClass != null ) {
throw new DuplicateMappingException(
String.format(
"The [%s] and [%s] entities share the same JPA entity name: [%s] which is not allowed!",
matchingPersistentClass.getClassName(),
persistentClass.getClassName(),
jpaEntityName
),
DuplicateMappingException.Type.ENTITY,
jpaEntityName
);
}
entityBindingMap.put( entityName, persistentClass );
}

View File

@ -0,0 +1,56 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
//$Id$
package org.hibernate.test.annotations.access;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Transient;
import org.hibernate.annotations.AccessType;
/**
* @author Vlad Mihalcea
*/
@MappedSuperclass
public class BaseFurniture extends Woody {
@Id
@GeneratedValue
private Integer id;
private String brand;
@Transient
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@AccessType("property")
public long weight;
public long getWeight() {
return weight + 1;
}
public void setWeight(long weight) {
this.weight = weight + 1;
}
}

View File

@ -14,7 +14,7 @@
* @author Hardy Ferentschik
*/
@Embeddable
public class Closet extends Furniture {
public class Closet extends BaseFurniture {
int numberOfDoors;
}

View File

@ -19,38 +19,6 @@
*/
@Entity
@AccessType("field")
public class Furniture extends Woody {
@Id
@GeneratedValue
private Integer id;
public class Furniture extends BaseFurniture {
private String brand;
@Transient
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@AccessType("property")
public long weight;
public long getWeight() {
return weight + 1;
}
public void setWeight(long weight) {
this.weight = weight + 1;
}
}

View File

@ -9,6 +9,7 @@
package org.hibernate.test.annotations.duplicatedgenerator;
import org.hibernate.AnnotationException;
import org.hibernate.DuplicateMappingException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.Environment;
@ -31,13 +32,14 @@ public void testDuplicateEntityName() throws Exception {
try {
cfg.addAnnotatedClass( Flight.class );
cfg.addAnnotatedClass( org.hibernate.test.annotations.Flight.class );
cfg.addAnnotatedClass( org.hibernate.test.annotations.Company.class );
cfg.addResource( "org/hibernate/test/annotations/orm.xml" );
cfg.addResource( "org/hibernate/test/annotations/duplicatedgenerator/orm.xml" );
serviceRegistry = ServiceRegistryBuilder.buildServiceRegistry( cfg.getProperties() );
sf = cfg.buildSessionFactory( serviceRegistry );
Assert.fail( "Should not be able to map the same entity name twice" );
}
catch (AnnotationException ae) {
catch (DuplicateMappingException ae) {
//success
}
finally {

View File

@ -0,0 +1,65 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.entityname;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.DuplicateMappingException;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-13060" )
public class DuplicateEntityNameTest extends BaseCoreFunctionalTestCase {
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] {
Purchase1.class,
Purchase2.class
};
}
@Override
protected void buildSessionFactory() {
try {
super.buildSessionFactory();
fail("Should throw DuplicateMappingException");
}
catch (DuplicateMappingException e) {
assertEquals( "The [org.hibernate.test.entityname.DuplicateEntityNameTest$Purchase1] and [org.hibernate.test.entityname.DuplicateEntityNameTest$Purchase2] entities share the same JPA entity name: [Purchase] which is not allowed!", e.getMessage() );
}
}
@Test
public void test() {
}
@Entity(name = "Purchase")
@Table(name="purchase_old")
public static class Purchase1 {
@Id
public String uid;
}
@Entity(name = "Purchase")
@Table(name="purchase_new")
public static class Purchase2 {
@Id
public String uid;
}
}

View File

@ -140,7 +140,7 @@ public static class Role {
public static class User extends Role {
}
@Entity(name = "User")
@Entity(name = "Person")
@DiscriminatorValue("8")
@PrimaryKeyJoinColumn(name = "USER_ID")
public static class Person extends Role {