diff --git a/annotations/src/main/java/org/hibernate/annotations/Subselect.java b/annotations/src/main/java/org/hibernate/annotations/Subselect.java new file mode 100644 index 0000000000..d55f169e19 --- /dev/null +++ b/annotations/src/main/java/org/hibernate/annotations/Subselect.java @@ -0,0 +1,18 @@ +package org.hibernate.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Map an immutable and read-only entity to a given SQL subselect expression: + * @author Sharath Reddy + * + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface Subselect { + String value(); +} diff --git a/annotations/src/main/java/org/hibernate/annotations/Synchronize.java b/annotations/src/main/java/org/hibernate/annotations/Synchronize.java new file mode 100644 index 0000000000..c36678949e --- /dev/null +++ b/annotations/src/main/java/org/hibernate/annotations/Synchronize.java @@ -0,0 +1,21 @@ +package org.hibernate.annotations; + +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Ensures that auto-flush happens correctly and that queries against the derived + * entity do not return stale data. + * + * Mostly used with Subselect. + * + * @author Sharath Reddy + */ +@Target(TYPE) +@Retention(RUNTIME) +public @interface Synchronize { + String [] value(); +} diff --git a/annotations/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java b/annotations/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java index 10902b77a5..7f41ccdcda 100644 --- a/annotations/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java +++ b/annotations/src/main/java/org/hibernate/cfg/annotations/EntityBinder.java @@ -54,6 +54,8 @@ import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.SQLDeleteAll; import org.hibernate.annotations.SQLInsert; import org.hibernate.annotations.SQLUpdate; +import org.hibernate.annotations.Subselect; +import org.hibernate.annotations.Synchronize; import org.hibernate.annotations.Tables; import org.hibernate.annotations.Tuplizer; import org.hibernate.annotations.Tuplizers; @@ -120,7 +122,9 @@ public class EntityBinder { private boolean cacheLazyProperty; private AccessType propertyAccessType = AccessType.DEFAULT; private boolean wrapIdsInEmbeddedComponents; - + private String subselect; + + public boolean wrapIdsInEmbeddedComponents() { return wrapIdsInEmbeddedComponents; } @@ -257,6 +261,7 @@ public class EntityBinder { SQLDelete sqlDelete = annotatedClass.getAnnotation( SQLDelete.class ); SQLDeleteAll sqlDeleteAll = annotatedClass.getAnnotation( SQLDeleteAll.class ); Loader loader = annotatedClass.getAnnotation( Loader.class ); + if ( sqlInsert != null ) { persistentClass.setCustomSQLInsert( sqlInsert.sql().trim(), sqlInsert.callable(), ExecuteUpdateResultCheckStyle.parse( sqlInsert.check().toString().toLowerCase() ) @@ -282,6 +287,20 @@ public class EntityBinder { persistentClass.setLoaderName( loader.namedQuery() ); } + if ( annotatedClass.isAnnotationPresent( Synchronize.class )) { + Synchronize synchronizedWith = annotatedClass.getAnnotation(Synchronize.class); + + String [] tables = synchronizedWith.value(); + for (String table : tables) { + persistentClass.addSynchronizedTable(table); + } + } + + if ( annotatedClass.isAnnotationPresent(Subselect.class )) { + Subselect subselect = annotatedClass.getAnnotation(Subselect.class); + this.subselect = subselect.value(); + } + //tuplizers if ( annotatedClass.isAnnotationPresent( Tuplizers.class ) ) { for (Tuplizer tuplizer : annotatedClass.getAnnotation( Tuplizers.class ).value()) { @@ -474,7 +493,8 @@ public class EntityBinder { uniqueConstraints, constraints, denormalizedSuperclassTable, - mappings + mappings, + this.subselect ); if ( persistentClass instanceof TableOwner ) { @@ -701,7 +721,8 @@ public class EntityBinder { uniqueConstraintHolders, null, null, - mappings + mappings, + null ); //no check constraints available on joins diff --git a/annotations/src/main/java/org/hibernate/cfg/annotations/TableBinder.java b/annotations/src/main/java/org/hibernate/cfg/annotations/TableBinder.java index 53030f4874..d4a768ef7d 100644 --- a/annotations/src/main/java/org/hibernate/cfg/annotations/TableBinder.java +++ b/annotations/src/main/java/org/hibernate/cfg/annotations/TableBinder.java @@ -183,7 +183,8 @@ public class TableBinder { uniqueConstraints, constraints, denormalizedSuperTable, - mappings + mappings, + null ); } @@ -200,7 +201,7 @@ public class TableBinder { return new AssociationTableNameSource( name, logicalName ); } - + public static Table buildAndFillTable( String schema, String catalog, @@ -210,7 +211,8 @@ public class TableBinder { List uniqueConstraints, String constraints, Table denormalizedSuperTable, - ExtendedMappings mappings) { + ExtendedMappings mappings, + String subselect) { schema = BinderHelper.isDefault( schema ) ? mappings.getSchemaName() : schema; catalog = BinderHelper.isDefault( catalog ) ? mappings.getCatalogName() : catalog; @@ -226,7 +228,7 @@ public class TableBinder { catalog, realTableName, isAbstract, - null, // subselect + subselect, denormalizedSuperTable ); } @@ -235,7 +237,7 @@ public class TableBinder { schema, catalog, realTableName, - null, // subselect + subselect, isAbstract ); } diff --git a/annotations/src/test/java/org/hibernate/test/annotations/subselect/Bid.java b/annotations/src/test/java/org/hibernate/test/annotations/subselect/Bid.java new file mode 100644 index 0000000000..6d769dd26d --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/subselect/Bid.java @@ -0,0 +1,65 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat, Inc. and/or its affiliates or third- + * party contributors as indicated by the @author tags or express + * copyright attribution statements applied by the authors. + * All third-party contributions are distributed under license by + * Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to + * use, modify, copy, or redistribute it subject to the terms and + * conditions of the GNU Lesser General Public License, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this distribution; if not, write to: + * + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ +package org.hibernate.test.annotations.subselect; + +import javax.persistence.Entity; +import javax.persistence.Id; + + +/** + * @author Sharath Reddy + */ +@Entity +public class Bid { + + private int id; + private long itemId; + private double amount; + + @Id + public int getId() { + return id; + } + public void setId(int id) { + this.id = id; + } + public long getItemId() { + return itemId; + } + public void setItemId(long itemId) { + this.itemId = itemId; + } + public double getAmount() { + return amount; + } + public void setAmount(double val) { + this.amount = val; + } + + + +} diff --git a/annotations/src/test/java/org/hibernate/test/annotations/subselect/HighestBid.java b/annotations/src/test/java/org/hibernate/test/annotations/subselect/HighestBid.java new file mode 100644 index 0000000000..fde5b54b46 --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/subselect/HighestBid.java @@ -0,0 +1,64 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat, Inc. and/or its affiliates or third- + * party contributors as indicated by the @author tags or express + * copyright attribution statements applied by the authors. + * All third-party contributions are distributed under license by + * Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to + * use, modify, copy, or redistribute it subject to the terms and + * conditions of the GNU Lesser General Public License, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this distribution; if not, write to: + * + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +package org.hibernate.test.annotations.subselect; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.annotations.Subselect; +import org.hibernate.annotations.Synchronize; + +/** + * @author Sharath Reddy + * + */ +@Entity +@Subselect("select item.name as name, max(bid.amount) as amount from item, bid where bid.itemId = item.id group by item.name") +@Synchronize({"Item", "Bid"}) +public class HighestBid { + + private String name; + private double amount; + + @Id + public String getName() { + return name; + } + public void setName(String val) { + this.name = val; + } + public double getAmount() { + return amount; + } + public void setAmount(double amount) { + this.amount = amount; + } + + + +} diff --git a/annotations/src/test/java/org/hibernate/test/annotations/subselect/Item.java b/annotations/src/test/java/org/hibernate/test/annotations/subselect/Item.java new file mode 100644 index 0000000000..2576f9e04e --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/subselect/Item.java @@ -0,0 +1,58 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat, Inc. and/or its affiliates or third- + * party contributors as indicated by the @author tags or express + * copyright attribution statements applied by the authors. + * All third-party contributions are distributed under license by + * Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to + * use, modify, copy, or redistribute it subject to the terms and + * conditions of the GNU Lesser General Public License, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this distribution; if not, write to: + * + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +package org.hibernate.test.annotations.subselect; + + +import javax.persistence.Entity; +import javax.persistence.Id; + +/** + * @author Sharath Reddy + */ +@Entity +public class Item { + + private long id; + private String name; + + @Id + public long getId() { + return id; + } + public void setId(long id) { + this.id = id; + } + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + +} diff --git a/annotations/src/test/java/org/hibernate/test/annotations/subselect/SubselectTest.java b/annotations/src/test/java/org/hibernate/test/annotations/subselect/SubselectTest.java new file mode 100644 index 0000000000..5b20171558 --- /dev/null +++ b/annotations/src/test/java/org/hibernate/test/annotations/subselect/SubselectTest.java @@ -0,0 +1,89 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2009, Red Hat, Inc. and/or its affiliates or third- + * party contributors as indicated by the @author tags or express + * copyright attribution statements applied by the authors. + * All third-party contributions are distributed under license by + * Red Hat, Inc. + * + * This copyrighted material is made available to anyone wishing to + * use, modify, copy, or redistribute it subject to the terms and + * conditions of the GNU Lesser General Public License, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this distribution; if not, write to: + * + * Free Software Foundation, Inc. + * 51 Franklin Street, Fifth Floor + * Boston, MA 02110-1301 USA + */ + +package org.hibernate.test.annotations.subselect; + +import org.hibernate.Hibernate; +import org.hibernate.Query; +import org.hibernate.Session; +import org.hibernate.Transaction; +import org.hibernate.test.annotations.TestCase; + + +/** + * @author Sharath Reddy + */ +public class SubselectTest extends TestCase { + + public void testSubselectWithSynchronize() { + + Session s = openSession(); + Transaction tx = s.beginTransaction(); + tx.begin(); + + //We don't use auto-generated ids because these seem to cause the session to flush. + //We want to test that the session flushes because of the 'synchronize' annotation + long itemId = 1; + Item item = new Item(); + item.setName("widget"); + item.setId(itemId); + s.save(item); + + Bid bid1 = new Bid(); + bid1.setAmount(100.0); + bid1.setItemId(itemId); + bid1.setId(1); + s.save(bid1); + + Bid bid2 = new Bid(); + bid2.setAmount(200.0); + bid2.setItemId(itemId); + bid2.setId(2); + s.save(bid2); + + //Because we use 'synchronize' annotation, this query should trigger session flush + Query query = s.createQuery("from HighestBid b where b.name = :name"); + query.setParameter("name", "widget", Hibernate.STRING); + HighestBid highestBid = (HighestBid) query.list().iterator().next(); + + assertEquals(200.0, highestBid.getAmount()); + + s.close(); + + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ + Item.class, + Bid.class, + HighestBid.class + }; + } + +}