diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 9cce99dfc2..91fe4133a4 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -12,6 +12,7 @@ import java.util.Map; import java.util.Random; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; +import javax.persistence.InheritanceType; import javax.persistence.MapKeyClass; import org.hibernate.AnnotationException; @@ -30,6 +31,7 @@ import org.hibernate.cfg.CollectionPropertyHolder; import org.hibernate.cfg.CollectionSecondPass; import org.hibernate.cfg.Ejb3Column; import org.hibernate.cfg.Ejb3JoinColumn; +import org.hibernate.cfg.InheritanceState; import org.hibernate.cfg.PropertyData; import org.hibernate.cfg.PropertyHolderBuilder; import org.hibernate.cfg.PropertyPreloadedData; @@ -46,6 +48,7 @@ import org.hibernate.mapping.ManyToOne; import org.hibernate.mapping.OneToMany; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; +import org.hibernate.mapping.Selectable; import org.hibernate.mapping.SimpleValue; import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; @@ -121,8 +124,13 @@ public class MapBinder extends CollectionBinder { ); } org.hibernate.mapping.Map map = (org.hibernate.mapping.Map) this.collection; + // HHH-11005 - if InheritanceType.JOINED then need to find class defining the column + InheritanceState inheritanceState = inheritanceStatePerClass.get( collType ); + PersistentClass targetPropertyPersistentClass = InheritanceType.JOINED.equals( inheritanceState.getType() ) ? + mapProperty.getPersistentClass() : + associatedClass; Value indexValue = createFormulatedValue( - mapProperty.getValue(), map, targetPropertyName, associatedClass, buildingContext + mapProperty.getValue(), map, targetPropertyName, associatedClass, targetPropertyPersistentClass, buildingContext ); map.setIndex( indexValue ); } @@ -305,6 +313,7 @@ public class MapBinder extends CollectionBinder { Collection collection, String targetPropertyName, PersistentClass associatedClass, + PersistentClass targetPropertyPersistentClass, MetadataBuildingContext buildingContext) { Value element = collection.getElement(); String fromAndWhere = null; @@ -322,7 +331,7 @@ public class MapBinder extends CollectionBinder { throw new AnnotationException( "SecondaryTable JoinColumn cannot reference a non primary key" ); } } - Iterator referencedEntityColumns; + Iterator referencedEntityColumns; if ( referencedPropertyName == null ) { referencedEntityColumns = associatedClass.getIdentifier().getColumnIterator(); } @@ -330,20 +339,23 @@ public class MapBinder extends CollectionBinder { Property referencedProperty = associatedClass.getRecursiveProperty( referencedPropertyName ); referencedEntityColumns = referencedProperty.getColumnIterator(); } - String alias = "$alias$"; - StringBuilder fromAndWhereSb = new StringBuilder( " from " ) - .append( associatedClass.getTable().getName() ) - //.append(" as ") //Oracle doesn't support it in subqueries - .append( " " ) - .append( alias ).append( " where " ); - Iterator collectionTableColumns = element.getColumnIterator(); - while ( collectionTableColumns.hasNext() ) { - Column colColumn = (Column) collectionTableColumns.next(); - Column refColumn = (Column) referencedEntityColumns.next(); - fromAndWhereSb.append( alias ).append( '.' ).append( refColumn.getQuotedName() ) - .append( '=' ).append( colColumn.getQuotedName() ).append( " and " ); + fromAndWhere = getFromAndWhereFormula( + associatedClass.getTable().getName(), + element.getColumnIterator(), + referencedEntityColumns + ); + } + else { + // HHH-11005 - only if we are OneToMany and location of map key property is at a different level, need to add a select + if ( !associatedClass.equals( targetPropertyPersistentClass ) ) { + fromAndWhere = getFromAndWhereFormula( + targetPropertyPersistentClass.getTable() + .getQualifiedTableName() + .toString(), + element.getColumnIterator(), + associatedClass.getIdentifier().getColumnIterator() + ); } - fromAndWhere = fromAndWhereSb.substring( 0, fromAndWhereSb.length() - 5 ); } if ( value instanceof Component ) { @@ -368,7 +380,7 @@ public class MapBinder extends CollectionBinder { newProperty.setSelectable( current.isSelectable() ); newProperty.setValue( createFormulatedValue( - current.getValue(), collection, targetPropertyName, associatedClass, buildingContext + current.getValue(), collection, targetPropertyName, associatedClass, associatedClass, buildingContext ) ); indexComponent.addProperty( newProperty ); @@ -425,4 +437,27 @@ public class MapBinder extends CollectionBinder { throw new AssertionFailure( "Unknown type encounters for map key: " + value.getClass() ); } } + + private String getFromAndWhereFormula( + String tableName, + Iterator collectionTableColumns, + Iterator referencedEntityColumns) { + String alias = "$alias$"; + StringBuilder fromAndWhereSb = new StringBuilder( " from " ) + .append( tableName ) + //.append(" as ") //Oracle doesn't support it in subqueries + .append( " " ) + .append( alias ).append( " where " ); + while ( collectionTableColumns.hasNext() ) { + Column colColumn = (Column) collectionTableColumns.next(); + Column refColumn = (Column) referencedEntityColumns.next(); + fromAndWhereSb.append( alias ) + .append( '.' ) + .append( refColumn.getQuotedName() ) + .append( '=' ) + .append( colColumn.getQuotedName() ) + .append( " and " ); + } + return fromAndWhereSb.substring( 0, fromAndWhereSb.length() - 5 ); + } } diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Book.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Book.java new file mode 100644 index 0000000000..0434e65fda --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Book.java @@ -0,0 +1,36 @@ +package org.hibernate.test.onetomany.inheritance.joined; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name="BOOKTABJO") +public class Book extends Product { + + private String isbn; + + @ManyToOne + private Library library; + + public Book() { + super(); + } + + public Book(String inventoryCode, String isbn) { + super(inventoryCode); + this.isbn = isbn; + } + + public String getIsbn() { + return isbn; + } + + public Library getLibrary() { + return library; + } + + public void setLibrary(Library library) { + this.library = library; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Library.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Library.java new file mode 100644 index 0000000000..d595fd5833 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Library.java @@ -0,0 +1,52 @@ +package org.hibernate.test.onetomany.inheritance.joined; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name="LIBRARYJO") +public class Library { + + @Id + @GeneratedValue + private int entid; + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="inventoryCode") + private Map booksOnInventory = new HashMap<>(); + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="isbn") + private Map booksOnIsbn = new HashMap<>(); + + public int getEntid() { + return entid; + } + + public Map getBooksOnInventory() { + return booksOnInventory; + } + + public Map getBooksOnIsbn() { + return booksOnIsbn; + } + + public void addBook(Book book) { + book.setLibrary( this ); + booksOnInventory.put( book.getInventoryCode(), book ); + booksOnIsbn.put( book.getIsbn(), book ); + } + + public void removeBook(Book book) { + book.setLibrary( null ); + booksOnInventory.remove( book.getInventoryCode() ); + booksOnIsbn.remove( book.getIsbn() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/MappedSuperclassMapTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/MappedSuperclassMapTest.java new file mode 100644 index 0000000000..77b9cd5d63 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/MappedSuperclassMapTest.java @@ -0,0 +1,142 @@ +package org.hibernate.test.onetomany.inheritance.joined; + +import java.util.List; +import java.util.Map.Entry; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11005") +public class MappedSuperclassMapTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MappedSuperclassMapTest.class ); + + private static final String SKU001 = "SKU001"; + private static final String SKU002 = "SKU002"; + private static final String WAR_AND_PEACE = "0140447938"; + private static final String ANNA_KARENINA = "0140449175"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Book.class, + Library.class, + Product.class + }; + } + + @Before + public void init() { + doInHibernate( this::sessionFactory, sess -> { + Book book1 = new Book( SKU001, WAR_AND_PEACE); + Book book2 = new Book( SKU002, ANNA_KARENINA); + sess.persist( book1 ); + sess.flush(); + sess.persist( book2 ); + sess.flush(); + Library library = new Library(); + library.addBook( book1 ); + library.addBook( book2 ); + sess.persist(library); + } ); + } + + @Test + public void lookupEntities() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + Book book = library.getBooksOnInventory().get( SKU001); + assertNotNull(book); + Library Library = library; + Library.getBooksOnIsbn().get( WAR_AND_PEACE ); + assertEquals(WAR_AND_PEACE, book.getIsbn()); + + book = library.getBooksOnInventory().get(SKU002); + assertNotNull(book); + assertEquals(ANNA_KARENINA, book.getIsbn()); + } ); + } + + @Test + public void lookupEntities_entrySet() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + for (Entry entry : library.getBooksOnInventory().entrySet()) { + log.info("Found SKU " + entry.getKey() + " with ISBN " + entry.getValue().getIsbn()); + } + } ); + } + + @Test + public void breakReferences() { + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + assertNotNull(book.getLibrary()); + log.info("Found SKU " + book.getInventoryCode() + " with library " + book.getLibrary().getEntid()); + } + + for (Book book : books) { + book.getLibrary().removeBook( book ); + } + } ); + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + if (book.getLibrary() == null ) { + log.info("Found SKU " + book.getInventoryCode() + " with no library"); + } + } + + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(0, library.getBooksOnInventory().size()); + log.info("Found Library " + library.getEntid() + " with no books"); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected void cleanupTestData() throws Exception { + doInHibernate( this::sessionFactory, sess -> { + sess.createQuery( "delete from Book" ).executeUpdate(); + sess.createQuery( "delete from Library" ).executeUpdate(); + } ); + } + + @Override + protected boolean rebuildSessionFactoryOnError() { + return false; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Product.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Product.java new file mode 100644 index 0000000000..ff3480eda2 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/joined/Product.java @@ -0,0 +1,38 @@ +package org.hibernate.test.onetomany.inheritance.joined; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity +@Table(name="PRODTABJO") +@Inheritance(strategy=InheritanceType.JOINED) +public abstract class Product { + + @Id + @GeneratedValue + private int entid; + + @Column(name="INVCODE") + private String inventoryCode; + + public Product() { + + } + + public Product(String inventoryCode) { + this.inventoryCode = inventoryCode; + } + + public int getEntid() { + return entid; + } + + public String getInventoryCode() { + return inventoryCode; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Book.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Book.java new file mode 100644 index 0000000000..5c1862b658 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Book.java @@ -0,0 +1,36 @@ +package org.hibernate.test.onetomany.inheritance.perclass; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name="BOOKTABPC") +public class Book extends Product { + + private String isbn; + + @ManyToOne(targetEntity=Library.class) + private Library library; + + public Book() { + super(); + } + + public Book(String inventoryCode, String isbn) { + super(inventoryCode); + this.isbn = isbn; + } + + public String getIsbn() { + return isbn; + } + + public Library getLibrary() { + return library; + } + + public void setLibrary(Library library) { + this.library = library; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Library.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Library.java new file mode 100644 index 0000000000..03b5ed2c56 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Library.java @@ -0,0 +1,52 @@ +package org.hibernate.test.onetomany.inheritance.perclass; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name="LIBRARYPC") +public class Library { + + @Id + @GeneratedValue + private int entid; + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="inventoryCode") + private Map booksOnInventory = new HashMap<>(); + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="isbn") + private Map booksOnIsbn = new HashMap<>(); + + public int getEntid() { + return entid; + } + + public Map getBooksOnInventory() { + return booksOnInventory; + } + + public Map getBooksOnIsbn() { + return booksOnIsbn; + } + + public void addBook(Book book) { + book.setLibrary( this ); + booksOnInventory.put( book.getInventoryCode(), book ); + booksOnIsbn.put( book.getIsbn(), book ); + } + + public void removeBook(Book book) { + book.setLibrary( null ); + booksOnInventory.remove( book.getInventoryCode() ); + booksOnIsbn.remove( book.getIsbn() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/MappedSuperclassMapTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/MappedSuperclassMapTest.java new file mode 100644 index 0000000000..e68ec229af --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/MappedSuperclassMapTest.java @@ -0,0 +1,136 @@ +package org.hibernate.test.onetomany.inheritance.perclass; + +import java.util.List; +import java.util.Map.Entry; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11005") +public class MappedSuperclassMapTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MappedSuperclassMapTest.class ); + + private static final String SKU001 = "SKU001"; + private static final String SKU002 = "SKU002"; + private static final String WAR_AND_PEACE = "0140447938"; + private static final String ANNA_KARENINA = "0140449175"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Book.class, + Library.class, + Product.class + }; + } + + @Before + public void init() { + doInHibernate( this::sessionFactory, sess -> { + Library library = new Library(); + library.addBook( new Book( SKU001, WAR_AND_PEACE) ); + library.addBook( new Book( SKU002, ANNA_KARENINA) ); + sess.persist(library); + } ); + } + + @Test + public void lookupEntities() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + Book book = library.getBooksOnInventory().get( SKU001); + assertNotNull(book); + Library Library = library; + Library.getBooksOnIsbn().get( WAR_AND_PEACE ); + assertEquals(WAR_AND_PEACE, book.getIsbn()); + + book = library.getBooksOnInventory().get(SKU002); + assertNotNull(book); + assertEquals(ANNA_KARENINA, book.getIsbn()); + } ); + } + + @Test + public void lookupEntities_entrySet() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + for (Entry entry : library.getBooksOnInventory().entrySet()) { + log.info("Found SKU " + entry.getKey() + " with ISBN " + entry.getValue().getIsbn()); + } + } ); + } + + @Test + public void breakReferences() { + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + assertNotNull(book.getLibrary()); + log.info("Found SKU " + book.getInventoryCode() + " with library " + book.getLibrary().getEntid()); + } + + for (Book book : books) { + book.getLibrary().removeBook( book ); + } + } ); + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + if (book.getLibrary() == null ) { + log.info("Found SKU " + book.getInventoryCode() + " with no library"); + } + } + + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(0, library.getBooksOnInventory().size()); + log.info("Found Library " + library.getEntid() + " with no books"); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected void cleanupTestData() throws Exception { + doInHibernate( this::sessionFactory, sess -> { + sess.createQuery( "delete from Book" ).executeUpdate(); + sess.createQuery( "delete from Library" ).executeUpdate(); + } ); + } + + @Override + protected boolean rebuildSessionFactoryOnError() { + return false; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Product.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Product.java new file mode 100644 index 0000000000..2978345fbf --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/perclass/Product.java @@ -0,0 +1,38 @@ +package org.hibernate.test.onetomany.inheritance.perclass; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity +@Table(name="PRODTABPC") +@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS) +public abstract class Product { + + @Id + @GeneratedValue + private int entid; + + @Column(name="INVCODE") + private String inventoryCode; + + public Product() { + + } + + public Product(String inventoryCode) { + this.inventoryCode = inventoryCode; + } + + public int getEntid() { + return entid; + } + + public String getInventoryCode() { + return inventoryCode; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Book.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Book.java new file mode 100644 index 0000000000..b14ddf6d5c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Book.java @@ -0,0 +1,35 @@ +package org.hibernate.test.onetomany.inheritance.single; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; + + +@Entity +public class Book extends Product { + + private String isbn; + + @ManyToOne + private Library library; + + public Book() { + super(); + } + + public Book(String inventoryCode, String isbn) { + super(inventoryCode); + this.isbn = isbn; + } + + public String getIsbn() { + return isbn; + } + + public Library getLibrary() { + return library; + } + + public void setLibrary(Library library) { + this.library = library; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Library.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Library.java new file mode 100644 index 0000000000..8604a8190c --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Library.java @@ -0,0 +1,53 @@ +package org.hibernate.test.onetomany.inheritance.single; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.Table; + + +@Entity +@Table(name="INVENTORYSG") +public class Library { + + @Id + @GeneratedValue + private int entid; + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="inventoryCode") + private Map booksOnInventory = new HashMap<>(); + + @OneToMany(mappedBy="library", cascade = CascadeType.ALL) + @MapKey(name="isbn") + private Map booksOnIsbn = new HashMap<>(); + + public int getEntid() { + return entid; + } + + public Map getBooksOnInventory() { + return booksOnInventory; + } + + public Map getBooksOnIsbn() { + return booksOnIsbn; + } + + public void addBook(Book book) { + book.setLibrary( this ); + booksOnInventory.put( book.getInventoryCode(), book ); + booksOnIsbn.put( book.getIsbn(), book ); + } + + public void removeBook(Book book) { + book.setLibrary( null ); + booksOnInventory.remove( book.getInventoryCode() ); + booksOnIsbn.remove( book.getIsbn() ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/MappedSuperclassMapTest.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/MappedSuperclassMapTest.java new file mode 100644 index 0000000000..59333c0f31 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/MappedSuperclassMapTest.java @@ -0,0 +1,136 @@ +package org.hibernate.test.onetomany.inheritance.single; + +import java.util.List; +import java.util.Map.Entry; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Before; +import org.junit.Test; + +import org.jboss.logging.Logger; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@TestForIssue(jiraKey = "HHH-11005") +public class MappedSuperclassMapTest extends BaseNonConfigCoreFunctionalTestCase { + + private static final Logger log = Logger.getLogger( MappedSuperclassMapTest.class ); + + private static final String SKU001 = "SKU001"; + private static final String SKU002 = "SKU002"; + private static final String WAR_AND_PEACE = "0140447938"; + private static final String ANNA_KARENINA = "0140449175"; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Book.class, + Library.class, + Product.class + }; + } + + @Before + public void init() { + doInHibernate( this::sessionFactory, sess -> { + Library library = new Library(); + library.addBook( new Book( SKU001, WAR_AND_PEACE) ); + library.addBook( new Book( SKU002, ANNA_KARENINA) ); + sess.persist(library); + } ); + } + + @Test + public void lookupEntities() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + Book book = library.getBooksOnInventory().get( SKU001); + assertNotNull(book); + Library Library = library; + Library.getBooksOnIsbn().get( WAR_AND_PEACE ); + assertEquals(WAR_AND_PEACE, book.getIsbn()); + + book = library.getBooksOnInventory().get(SKU002); + assertNotNull(book); + assertEquals(ANNA_KARENINA, book.getIsbn()); + } ); + } + + @Test + public void lookupEntities_entrySet() { + doInHibernate( this::sessionFactory, sess -> { + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(2, library.getBooksOnInventory().size()); + + for (Entry entry : library.getBooksOnInventory().entrySet()) { + log.info("Found SKU " + entry.getKey() + " with ISBN " + entry.getValue().getIsbn()); + } + } ); + } + + @Test + public void breakReferences() { + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + assertNotNull(book.getLibrary()); + log.info("Found SKU " + book.getInventoryCode() + " with library " + book.getLibrary().getEntid()); + } + + for (Book book : books) { + book.getLibrary().removeBook( book ); + } + } ); + doInHibernate( this::sessionFactory, sess -> { + List books = sess.createQuery( "FROM Book").list(); + assertEquals(2, books.size()); + + for (Book book : books) { + if (book.getLibrary() == null ) { + log.info("Found SKU " + book.getInventoryCode() + " with no library"); + } + } + + List libraries = sess.createQuery( "FROM Library").list(); + assertEquals(1, libraries.size()); + Library library = libraries.get( 0); + assertNotNull(library); + + assertEquals(0, library.getBooksOnInventory().size()); + log.info("Found Library " + library.getEntid() + " with no books"); + } ); + } + + @Override + protected boolean isCleanupTestDataRequired() { + return true; + } + + @Override + protected void cleanupTestData() throws Exception { + doInHibernate( this::sessionFactory, sess -> { + sess.createQuery( "delete from Book" ).executeUpdate(); + sess.createQuery( "delete from Library" ).executeUpdate(); + } ); + } + + @Override + protected boolean rebuildSessionFactoryOnError() { + return false; + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Product.java b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Product.java new file mode 100644 index 0000000000..4356baf119 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/onetomany/inheritance/single/Product.java @@ -0,0 +1,38 @@ +package org.hibernate.test.onetomany.inheritance.single; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity +@Table(name="PRODTABSG") +@Inheritance(strategy=InheritanceType.SINGLE_TABLE) +public abstract class Product { + + @Id + @GeneratedValue + private int entid; + + @Column(name="INVCODE") + private String inventoryCode; + + public Product() { + + } + + public Product(String inventoryCode) { + this.inventoryCode = inventoryCode; + } + + public int getEntid() { + return entid; + } + + public String getInventoryCode() { + return inventoryCode; + } +} diff --git a/hibernate-orm b/hibernate-orm new file mode 160000 index 0000000000..f5a06771f7 --- /dev/null +++ b/hibernate-orm @@ -0,0 +1 @@ +Subproject commit f5a06771f77503bb72bb7c8fed11e0b9aab2fb17