I think everybody has at some point come across views in Database.Views generally represent a subset of the data contained in a table. A common scenario is one wherein we use soft deletion . For example instead of actually deleting an entity, we have a flag column in the row which we set as true indicating the entity has been deleted. We then create a view to access all the "alive" records.
Views thus provide us a filtered access of data in the table.(This is only one of its uses.) Hibernate provides something similar called dynamic views.
Consider a Shelf and Books on it. Some of the books have been marked as damaged/under repair. These books should not be available to certain modules of the application. The java classes are as below:
This can be achieved using data filters. let us look at the mapping for the book class:
The other new element of the <filter> element. This is defined inside the Book class and is applied to instances of Book class.
The filter condition is the actual condition that gets added to the SQL where clause. In this case we have added the restriction on the Under_Repair column. As the content is SQL we can also use native SQL operators or functions here. Now to actually use the filter created.
The logs indicate the same:
The data filter created does not apply to the load and get method. (However and HQL query of the form "from Book where id = 1" will return null if the row with id 1 fails the filter condition.) Also if I were to get a shelf entity and call shelf.getFilteredBooks() then all the books would be returned. The filter in this case is not applied on the collection.
It is possible to modify the Shelf class to ensure that the filter is applied to the books collection. The change is introduced in the mapping file for Shelf.
The logs also indicate that filtered collections do not work well with the cache. This is something that I will investigate later :)
Views thus provide us a filtered access of data in the table.(This is only one of its uses.) Hibernate provides something similar called dynamic views.
Consider a Shelf and Books on it. Some of the books have been marked as damaged/under repair. These books should not be available to certain modules of the application. The java classes are as below:
public class Shelf { private Integer id; private String code; private Set<Book> filteredBooks = new HashSet<Book>(); //setters - getters }
public class Book { private String name; private Integer id; private Shelf shelf; private boolean underRepair; }As can be seen, the book class has a property "underRepair" which indicates if the book is, well under repair. We would like to restrict such books from access to certain modules like Library module or Sale module.
This can be achieved using data filters. let us look at the mapping for the book class:
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.data.filter"> <class name="Book" table="BOOK"> <id name="id" type="integer"> <column name="ID" /> <generator class="native" /> </id> <property name="name" type="string"> <column name="Name" length="50" not-null="true" /> </property> <property name="underRepair" type="boolean"> <column name="UNDER_REPAIR" not-null="true" /> </property> <many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK1"> <column name="shelf_id"></column> </many-to-one> <filter name="booksWithRepairStatus" condition=" under_repair = :UnderRepairParam"> </filter> </class> <filter-def name="booksWithRepairStatus" > <!-- The filter accepts one runtime argument --> <filter-param name="UnderRepairParam" type="boolean" /> </filter-def> </hibernate-mapping>The hbm is more or less same as what we have created earlier. However two points of note - One is the <filter-def> element. This is where we define the filter. As can be seen it is not inside the class element but only within the <hibernate-mapping>. The filters are all defined at a global scope and hence must have a globally unique name. Our filter has been defined to accept one parameter with name UnderRepairParam of type boolean.
The other new element of the <filter> element. This is defined inside the Book class and is applied to instances of Book class.
The filter condition is the actual condition that gets added to the SQL where clause. In this case we have added the restriction on the Under_Repair column. As the content is SQL we can also use native SQL operators or functions here. Now to actually use the filter created.
public static void testHQlOnFilters() { final Session session = sessionFactory.openSession(); final List<Book> allbooks = session.createQuery("from Book").list(); int i = 0; System.out.println("All the books are : "); for (Book book : allbooks) { System.out.println( (++i) + " : " + "Name : " + book.getName() + " ,id " + book.getId() ); } session.close(); final Session filteredSession = sessionFactory.openSession(); final Filter filter = filteredSession.enableFilter("booksWithRepairStatus"); filter.setParameter("UnderRepairParam", true); filter.validate(); final List<Book> goodBooks = filteredSession.createQuery("from Book").list(); i = 0; System.out.println("All the filtered books are : "); for (Book book : goodBooks) { System.out.println( (++i) + " : " + "Name : " + book.getName() + " ,id " + book.getId() ); } //Also works for criteria objects filteredSession.close(); }The first session simply executes a select * on the Books table. In the second session, however we have enabled the filter on the session. The value of the parameter has then been applied. The filter has then been validated. The method call is optional and it throws a Hibernate exception if the filter parameters have not been set. The session is now ready to apply the filter on Book entities.
The logs indicate the same:
765 [main] INFO org.hibernate.cfg.HbmBinder - Mapping class: com.data.filter. Book -> BOOK ... 937 [main] DEBUG org.hibernate.cfg.HbmBinder - Applying filter [booksWithRepai rStatus] as [ under_repair = :UnderRepairParam] 937 [main] DEBUG org.hibernate.cfg.HbmBinder - Parsing filter-def [booksWithRe pairStatus] 937 [main] DEBUG org.hibernate.cfg.HbmBinder - adding filter parameter : Under RepairParam -> boolean 937 [main] DEBUG org.hibernate.cfg.HbmBinder - parameter heuristic type : org. hibernate.type.BooleanType@a8c488 937 [main] DEBUG org.hibernate.cfg.HbmBinder - Parsed filter-def [booksWithRep airStatus] ... 3156 [main] DEBUG org.hibernate.cfg.Configuration - Preparing to build session factory with filters : {booksWithRepairStatus=org.hibernate.engine.FilterDefinit ion@ce0c5} ... 3937 [main] INFO org.hibernate.impl.SessionFactoryImpl - building session fact ory 3937 [main] DEBUG org.hibernate.impl.SessionFactoryImpl - Session factory constructed with filter configurations : {booksWithRepairStatus=org.hibernate.engine. FilterDefinition@ce0c5} ... select book0_.ID as ID0_, book0_.Name as Name0_, book0_.UNDER_REPAIR as UNDER3_0_, book0_.shelf_id as shelf4_0_ from BOOK book0_ ... All the books are : 1 : Name : Lord Of The Rings ,id 1 2 : Name : To Fly ,id 2 3 : Name : Lakers ,id 3 ... 5234 [main] DEBUG org.hibernate.hql.ast.AST - --- SQL AST --- \-[SELECT] QueryNode:'SELECT' querySpaces (BOOK) +-[SELECT_CLAUSE] SelectClause:'{derived select clause}' | +-[SELECT_EXPR] SelectExpressionImpl: 'book0_.ID as ID0_' {FromElement{ex plicit,not a collection join,not a fetch join,fetch non-lazy properties,classAli as=null,role=null,tableName=BOOK,tableAlias=book0_,origin=null,colums={,classNam e=com.data.filter.Book}}} | \-[SQL_TOKEN] SqlFragment: 'book0_.Name as Name0_, book0_.UNDER_REPAIR as UNDER3_0_, book0_.shelf_id as shelf4_0_' +-[FROM] FromClause: 'from' FromClause{level=1, fromElementCounter=1, fromEl ements=1, fromElementByClassAlias=[], fromElementByTableAlias=[book0_], fromElem entsByPath=[], collectionJoinFromElementsByPath=[], impliedElements=[]} | \-[FROM_FRAGMENT] FromElement: 'BOOK book0_' FromElement{explicit,not a c ollection join,not a fetch join,fetch non-lazy properties,classAlias=null,role=n ull,tableName=BOOK,tableAlias=book0_,origin=null,colums={,className=com.data.fil ter.Book}} \-[WHERE] SqlNode: 'WHERE' \-[FILTERS] SqlNode: '{filter conditions}' \-[SQL_TOKEN] SqlFragment: ' book0_.under_repair = :booksWithRepairSta tus.UnderRepairParam' ... select book0_.ID as ID0_, book0_.Name as Name0_, book0_.UNDER_REPAIR as UNDER3_0_, book0_.shelf_id as shelf4_0_ from BOOK book0_ where book0_.under_repair = ? ... 5250 [main] DEBUG org.hibernate.type.BooleanType - binding 'true' to parameter: 1 ... All the filtered books are : 1 : Name : Lord Of The Rings ,id 1 2 : Name : Lakers ,id 3As can be seen the second query returned fewer books than the first one. All HQL and Criteria queries executed on the Book class via the second session will apply the filter on it.
The data filter created does not apply to the load and get method. (However and HQL query of the form "from Book where id = 1" will return null if the row with id 1 fails the filter condition.) Also if I were to get a shelf entity and call shelf.getFilteredBooks() then all the books would be returned. The filter in this case is not applied on the collection.
It is possible to modify the Shelf class to ensure that the filter is applied to the books collection. The change is introduced in the mapping file for Shelf.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.data.filter"> <class name="Shelf" table="SHELF"> <id name="id" type="integer"> <column name="ID" /> <generator class="native" /> </id> <property name="code" type="string"> <column name="CODE" length="50" not-null="true" /> </property> <set name="filteredBooks" cascade="save-update" inverse="true"> <key column="SHELF_ID" not-null="true" /> <one-to-many class="Book" /> <filter name="booksWithRepairStatus" condition=" under_repair = :UnderRepairParam"> </filter> </set> </class> </hibernate-mapping>As can be seen a filter element has been added to the set element. If were to navigate the relation now:
public static void testFilteredEntity() { final Session filteredSession = sessionFactory.openSession(); final Filter filter = filteredSession.enableFilter("booksWithRepairStatus"); filter.setParameter("UnderRepairParam", true); filter.validate(); final Shelf shelf = (Shelf) filteredSession.load(Shelf.class, 1); final Set<Book> books = shelf.getFilteredBooks(); int i = 0; for (Book book : books) { System.out.println( (++i) + " : " + "Name : " + book.getName() + " ,id " + book.getId() ); } }The logs indicate that only two books were retrieved:
2578 [main] DEBUG org.hibernate.event.def.DefaultInitializeCollectionEventListen er - disregarding cached version (if any) of collection due to enabled filters 2578 [main] DEBUG org.hibernate.SQL - select filteredbo0_.SHELF_ID as SHELF4_1_, filteredbo0_.ID as ID1_, filteredbo0_.ID as ID0_0_, filteredbo0_.Name as Name0_0_, filteredbo0_.UNDER_REPAIR as UNDER3_0_0_, filteredbo0_.shelf_id as shelf4_0_0_ from BOOK filteredbo0_ where filteredbo0_.under_repair = ? and filteredbo0_.SHELF_ID=? 2656 [main] DEBUG org.hibernate.event.def.DefaultInitializeCollectionEventListen er - collection initialized 1 : Name : Lord Of The Rings ,id 1 2 : Name : Lakers ,id 3The above filter will work only if the filter has been enabled on the session. Else it will ignore the filter settings and fire a normal select query.
The logs also indicate that filtered collections do not work well with the cache. This is something that I will investigate later :)
No comments:
Post a Comment