How do I use Hibernate's second level cache with JPA? - java

I am implementing an Entity Attribute Value based persistence mechanism. All DB access is done via Hibernate.
I have a table that contains paths for nodes, it is extremely simple, just an id, and a path (string) The paths would be small in number, around a few thousand.
The main table has millions of rows, and rather than repeating the paths, I've normalized the paths to their own table. The following is the behaviour I want, when inserting into main table
1) Check if the path exists in paths table (query via entity manager, using path value as parameter)
2) if it does not exist, insert, and get id (persist via entity manager)
3) put id as foreign key value to main table row, and insert this into main table.
This is going to happen thousands of times for a set of domain objects, which correspond to lots of rows in main table and some other tables. So the steps above are repeated using a single transaction like this:
EntityTransaction t = entityManager.getTransaction();
t.begin();
//perform steps given above, check, and then persist etc..
t.commit();
When I perform step 2, it introduces a huge performance drop to the total operation. It is begging for caching, because after a while that table will be at most 10-20k entries with very rare new inserts. I've tried to do this with Hibernate, and lost almost 2 days.
I'm using Hibernate 4.1, with JPA annotations and ECache. I've tried to enable query caching, even using the same query object throughout the inserts, as shown below:
Query call = entityManager.createQuery("select pt from NodePath pt " +
"where pt.path = :pathStr)");
call.setHint("org.hibernate.cacheable", true);
call.setParameter("pathStr", pPath);
List<NodePath> paths = call.getResultList();
if(paths.size() > 1)
throw new Exception("path table should have unique paths");
else if (paths.size() == 1){
NodePath path = paths.get(0);
return path.getId();
}
else {//paths null or has zero size
NodePath newPath = new NodePath();
newPath.setPath(pPath);
entityManager.persist(newPath);
return newPath.getId();
}
The NodePath entity is annotated as follows:
#Entity
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Table(name = "node_path", schema = "public")
public class NodePath implements java.io.Serializable {
The query cache is being used, as far as I can see from the statistics, but no use for second level cache is reported:
queries executed to database=1
query cache puts=1
query cache hits=689
query cache misses=1
....
second level cache puts=0
second level cache hits=0
second level cache misses=0
entities loaded=1
....
A simple, hand written hashtable as a cache, works as expected, cutting down total time drastically. I guess I'm failing to trigger Hibernate's caching due to nature of my operations.
How do I use hibernate's second level cache with this setup? For the record, this is my persistence xml:
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>...</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
<property name="hibernate.connection.password" value="zyx" />
<property name="hibernate.connection.url" value="jdbc:postgresql://192.168.0.194:5432/testdbforml" />
<property name="hibernate.connection.username" value="postgres"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.search.autoregister_listeners" value="false"/>
<property name="hibernate.jdbc.batch_size" value="200"/>
<property name="hibernate.connection.autocommit" value="false"/>
<property name="hibernate.generate_statistics" value="true"/>
<property name="hibernate.cache.use_structured_entries" value="true"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
</properties>

Ok, I found it.
My problem was that, cached query was keeping only Ids of query results in the cache, and it was (probably) going back to db to get the actual values, rather than getting them from the second level cache.
The problem is of course, the query did not put those values to second level cache, since they were not selected by primary id. So the solution is to use a method that will put values to second level cache, and with hibernate 4.1, I've manage to do this with natural id. Here is the function that either inserts or returns the value from cache, just in case it helps anybody else:
private UUID persistPath(String pPath) throws Exception{
org.hibernate.Session session = (Session) entityManager.getDelegate();
NodePath np = (NodePath) session.byNaturalId(NodePath.class).using("path", pPath).load();
if(np != null)
return np.getId();
else {//no such path entry, so let's create one
NodePath newPath = new NodePath();
newPath.setPath(pPath);
entityManager.persist(newPath);
return newPath.getId();
}
}

Related

Hibernate HQL Count and Implicit Join result are different

I have Class A with fields
Long id
B activity
And related class B
Long id
String name
Then I have the HQL query:
select a.id, a.activity.name from A a;
Some B fields on class A have null values, so left join gives more results than inner join.
I'm generating automatically the HQL, so I want the select and then the count:
select count(*) from A;
This gives me different results.
Is some way to resolve this, other than adding the explicit left join on the SQL (I'm receiving the HQL, can't change it).
Try execute the count with the same query, this way:
Query query = getEm().createQuery("select a.id, a.activity.name from A a");
int count = query.getResultList().size();
Or if you want to create a native query, configure your persistence.xml to enable log statement. So you can modify the original select to make the count.
Statement logging and statistics
hibernate.show_sql
hibernate.format_sql
hibernate.use_sql_comments
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />
<property name="show_sql" value="true" />
<property name="format_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>

"Missing table" on materialized view

I have the following entity:
#Entity
#Table(name = "full_address")
public class FullAddress {
#Id
#Column(name = "guid")
private String id;
#Column(name = "address")
private String address;
//getters and setters omitted
}
Also I create materialized view as follows:
CREATE TABLE address_table (
-- address fields,
aoid VARCHAR(36),
CONSTRAINT address_pk PRIMARY KEY (aoid)
);
CREATE MATERIALIZED VIEW full_address AS
SELECT buildFullAddressById(addrobj.aoid) AS address, addrobj.aoid AS guid FROM address_table AS addrobj;
-- buildFullAddressById is an sql function which is not important here
When I launch Tomcat application, i always get
org.hibernate.HibernateException: Missing table: full_address
Why is this happening? How to fix it?
UPD: looks like a bug in hibernate: https://hibernate.atlassian.net/browse/HHH-9602
I have had the same bug for several past days. As this answer said, it is possible to disable hibernate.hbm2ddl.auto property in your persistence.xml, but it is not a good idea if your project is rapidly developing.
TL;DR: set property hibernate.hbm2dll.extra_physical_table_types to MATERIALIZED VIEW.
Or add -Dhibernate.hbm2dll.extra_physical_table_types="MATERIALIZED VIEW" to VM options. But it is better to such options to configuration file.
Right now, we are using PostgreSQL 9.6 and Hibernate 5.2.12.Final. Somewhy, all materialized views validations were failing with the following exception:
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException:
Schema-validation: missing table [our_project_schema.mv_one_of_views]
All entities that successfully passed validation were either simple tables or views.
It seems like it is a default behaviour for generic databases. In sources here on lines 79-81 they add only these types:
final List<String> tableTypesList = new ArrayList<>();
tableTypesList.add( "TABLE" );
tableTypesList.add( "VIEW" );
Lines 85-87 tell us that there is a possibility to extend these hardcoded values with custom ones:
if ( extraPhysicalTableTypes != null ) {
Collections.addAll( tableTypesList, extraPhysicalTableTypes );
}
On line 56 it is declared private String[] extraPhysicalTableTypes;,
and on lines 71-77 there are some more values added into this array:
if ( !"".equals( extraPhysycalTableTypesConfig.trim() ) ) {
this.extraPhysicalTableTypes = StringHelper.splitTrimmingTokens(
",;",
extraPhysycalTableTypesConfig,
false
);
}
They come from lines 66-70, encoded as String under key EXTRA_PHYSICAL_TABLE_TYPES with empty default value:
final String extraPhysycalTableTypesConfig = configService.getSetting(
AvailableSettings.EXTRA_PHYSICAL_TABLE_TYPES,
StandardConverters.STRING,
""
);
And here on line 1545 is the declaration of that key:
/**
* Identifies a comma-separate list of values to specify extra table types,
* other than the default "TABLE" value, to recognize as defining a physical table
* by schema update, creation and validation.
*
* #since 5.0
*/
String EXTRA_PHYSICAL_TABLE_TYPES = "hibernate.hbm2dll.extra_physical_table_types";
So, adding this property will add another entry to tableTypesList that is used for filtering of many other entities in database, such as sequences, indices, temporary tables and others, that may have name similar to yours materialized view.
This is how my persistence.xml looks like, if you are interested:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="project-pu">
<jta-data-source>java:jboss/datasources/project-pu</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect"/>
<property name="hibernate.hbm2ddl.auto" value="validate"/>
<property name="hibernate.hbm2dll.extra_physical_table_types" value="MATERIALIZED VIEW"/>
<property name="hibernate.show_sql" value="false"/>
<property name="hibernate.format_sql" value="false"/>
<property name="hibernate.use_sql_comments" value="false"/>
<property name="hibernate.connection.url" value="jdbc:postgresql://localhost:5432/mgt"/>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver"/>
</properties>
</persistence-unit>
</persistence>
P.S. I know it is a very old post, but I fought with this problem for a few days. I failed finding an answer, so I decided to put it somewhere in the Internet. And this somewhere became here. :)
It's definitely a bug of Hibernate and was described here: https://hibernate.atlassian.net/browse/HHH-9602
So, I recommend removing hibernate.hbm2ddl.auto property as a workaround.
I found out the most simple solution (assuming you still want to use Hibernate to validate the schema) is to add a subselect to the table definition.
Using hbm.xml
<hibernate-mapping>
<class name="com.initech.MvObject" table="MV_OBJ">
<!-- This is necessary when using older Hibernate versions because of
Hibernate bugs. See
https://hibernate.atlassian.net/browse/HHH-1329
https://hibernate.atlassian.net/browse/HHH-9602
-->
<subselect>select * from MV_OBJ</subselect>
Using annotations
import org.hibernate.annotations.Subselect;
import javax.persistence.Entity;
#Entity
#Subselect("select * from MV_OBJ")
public class MvObj{
}

In Hibernate Component tag lazy loading is not working

I have a small hibernate application as above:
BankAccount class is as follows:
package in.co.way2learn;
import java.util.Set;
public class BankAccount {
private int accountNumber;
private String accountHoldersName;
private int balance;
private Address address;
private Set<String> emails;
//setters and getters
}
Address class is as below:
package in.co.way2learn;
public class Address {
private String addressLine1;
private String addressLine2;
private String city;
private String country;
private int pinCode;
//setters and getters
}
BankAccount.hbm.xml file is as below:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Jul 2, 2014 3:59:34 PM by Hibernate Tools 3.4.0.CR1 -->
<hibernate-mapping package="in.co.way2learn">
<class name="BankAccount">
<id name="accountNumber" type="integer">
<generator class="assigned"/>
</id>
<property name="accountHoldersName" type="string"/>
<property name="balance" type="integer"/>
<component name="address" class="Address" lazy="true">
<property name="addressLine1"/>
<property name="addressLine2"/>
<property name="city"/>
<property name="country"/>
<property name="pinCode"/>
</component>
<set name="emails" order-by="email asc" table="bankaccount_emails">
<key column="SNo"/>
<element column="email" type="string"/>
</set>
</class>
</hibernate-mapping>
hibernate.cfg.xml file is as below:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">
org.gjt.mm.mysql.Driver
</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.connection.url">
jdbc:mysql://localhost:3306/way2learnDB
</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.dialect">
org.hibernate.dialect.MySQLInnoDBDialect
</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<mapping resource="in/co/way2learn/BankAccount.hbm.xml"/>
</session-factory>
</hibernate-configuration>
Now my question is in BankAccount.hbm.xml file in the component tag I am using using lazy="true", when ever I am firing select query on BankAccount class using session.get(BankAccount.class, 1235); It is loading address details also from database, the code I used to fire select query is below:
Session session=sessionFactory.openSession();
Transaction transaction=session.beginTransaction();
BankAccount bankAccount=(BankAccount)session.get(BankAccount.class, 1235);
transaction.commit();
session.close();
The query fired is
Hibernate:
select
bankaccoun0_.accountNumber as accountN1_0_0_,
bankaccoun0_.accountHoldersName as accountH2_0_0_,
bankaccoun0_.balance as balance3_0_0_,
bankaccoun0_.addressLine1 as addressL4_0_0_,
bankaccoun0_.addressLine2 as addressL5_0_0_,
bankaccoun0_.city as city6_0_0_,
bankaccoun0_.country as country7_0_0_,
bankaccoun0_.pinCode as pinCode8_0_0_
from
BankAccount bankaccoun0_
where
bankaccoun0_.accountNumber=?
But I am expecting address details will be loaded lazily from database when ever I used bankAccount.getAddress() method only?
Now can any one please explain why hibernate is loading address details eagerly, and how to load then lazily?
Take an example from below code:-
class B {
private C cee;
public C getCee() {
return cee;
}
public void setCee(C cee) {
this.cee = cee;
}
}
class C {
// Not important really
}
Right after loading B, you may call getCee() to obtain C. But look, getCee() is a method of your class and Hibernate has no control over it. Hibernate does not know when someone is going to call getCee(). That means Hibernate must put an appropriate value into "cee" property at the moment it loads B from database.
If proxy is enabled for C, Hibernate can put a C-proxy object which is not loaded yet, but will be loaded when someone uses it. This gives lazy loading for one-to-one.
But now imagine your B object may or may not have associated C (constrained="false"). What should getCee() return when specific B does not have C? Null. But remember, Hibernate must set correct value of "cee" at the moment it set B (because it does no know when someone will call getCee()). Proxy does not help here because proxy itself in already non-null object.
If your B->C mapping is mandatory (constrained=true), Hibernate will use proxy for C resulting in lazy initialization. But if you allow B without C, Hibernate just HAS TO check presence of C at the moment it loads B. But a SELECT to check presence is just inefficient because the same SELECT may not just check presence, but load entire object. So lazy loading goes away.
Workaround1 : - Just add annotation or entry in hdm file for #JoinColumn for reference private Address address;.
Workaround2 :- add optional=false in OneToOne relationship
Other solutions for this problem:
The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.
The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add #LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.
The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown Java EE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case #LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.
This will work for you.
Hibernate does not create proxies for components, that's why lazy loading does not work for them.
Solutions:
Use bytecode instrumentation to enable lazy loading of non-entity fields. It has its own pitfalls and is not widely adopted.
Use two different classes for BankAccount, one containing the Address component (as it is now), and one without it, and map them to the same table. Then, use the one without address in contexts in which you don't need addresses.
Use fake one-to-one association between BankAccount and Address by making Address component an entity and mapping it to the same table. The drawback here is that you must not insert the Address instances (because you'll end up trying to insert a separate row in the table), but rather you have to read and update it after you insert the corresponding BankAccount entity instance.
Change the db schema and move the component to its own separate table. Then simply promote the component to an entity and map it to the new table.

Many concurrent reads + one write cause ObjectNotFoundException due to ehcache

I use Hibernate 3.6.8, ehcache 2.4.5 (also tried with latest 2.8.0), jvm 1.6.0_22 on a high traffic site, and sometimes I experience
ObjectNotFoundException: No row with the given identifier exists: [com.example.Foo#123]`
when a new Foo (in this case with id 123) is created via the simplest code possible:
Foo foo = new Foo();
session.save(foo);
The reason is that in all pages of this high traffic site I get all Foos like this:
session.createQuery("from Foo").setCacheable(true).list();
The table storing Foos contains 1000 rows, and the entity is cached in ehcache:
<class-cache class="com.example.Foo" usage="read-write" />
Other possibly relevant parts of my Hibernate configuration are:
<property name="connection.url">jdbc:mysql://localhost:3306/example?characterEncoding=UTF-8</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.acquire_increment">1</property>
<property name="hibernate.c3p0.idle_test_period">60</property>
<property name="hibernate.c3p0.min_size">10</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.max_statements">0</property>
<property name="hibernate.c3p0.timeout">0</property>
<property name="hibernate.c3p0.acquireRetryAttempts">1</property>
<property name="hibernate.c3p0.acquireRetryDelay">1</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.use_sql_comments">true</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.jdbc.use_scrollable_resultset">true</property>
<property name="hibernate.cache.provider_class">net.sf.ehcache.hibernate.SingletonEhCacheProvider</property>
<property name="net.sf.ehcache.configurationResourceName">/ehcache.xml</property>
<property name="hibernate.cache.use_query_cache">true</property>
The error happens once and then goes away. I suspect that the ehcache query cache is updated with the new entity id (123) id, but the entity cache is not yet updated with the contents of that entity. I reproduce this fairly easily locally using JMeter.
Any idea on how to solve this?
On Foo creation the ObjectNotFoundException is thrown once. If on the other hand I delete an instance of Foo then I constantly (and forever) get ObjectNotFoundException for each execution of .list(). The stacktrace can be seen at http://pastebin.com/raw.php?i=dp3HBgDB
The read-write strategy does not guarantee transactionality between the database and the cache, so I think this is what happens when the write occurs:
the new object Foo is attached to the hibernate session of the write.
a lazy loading proxy is inserted into the second level cache by the hibernate session associated to the write request.
The new Foo will be inserted in the database by that same session, but the insert will take a certain time to be built, flushed and committed.
meanwhile another request as hit the proxy to load all Foos. It finds the lazy loading proxy in the cache (see the stacktrace DefaultLoadEventListener.proxyOrLoad()), and decides to load the object (DefaultLoadEventListener.load()).
This triggers a Hibernate load() of a Foo not yet inserted in the database by the write thread.
No Foo with that Id is found on the database, so a ObjectNotFoundException is thrown.
To confirm this, put an exception breakpoint on your IDE, to see that at the moment the exception is thrown the object had not yet been inserted in the DB. One way to solve it would be to use the transactional strategy.
To mitigate the case where an entity is deleted and then list() does not work at all I've caught ObjectNotFoundException at a higher level and when this happens I do:
session.getSessionFactory().getCache().evictCollectionRegions();
session.getSessionFactory().getCache().evictDefaultQueryRegion();
session.getSessionFactory().getCache().evictQueryRegions();
Clearing the 2nd level cache makes the site work again. This of course doesn't prevent the problem from occuring, but it solves the downtime problem of the whole site.
Taken from the documentation on Cache Configuration:
The following attributes and elements are optional.
timeToIdleSeconds:
Sets the time to idle for an element before it expires.
i.e. The maximum amount of time between accesses before an element expires
Is only used if the element is not eternal.
Optional attribute. A value of 0 means that an Element can idle for infinity.
The default value is 0.
timeToLiveSeconds:
Sets the time to live for an element before it expires.
i.e. The maximum time between creation time and when an element expires.
Is only used if the element is not eternal.
Optional attribute. A value of 0 means that and Element can live for infinity.
The default value is 0.
Or you can also go with the alternate options:
Data Freshness and Expiration

Hibernate Delete query

When I try to delete an entry from a db, using
session.delete(object)
then I can the following:
1) If the row is present in DB then two SQL queries are getting executed: A select and then a delete
2) If the row is not present in the DB then only the select query is getting executed
But again this is not the case for update. Irrespective of the presence of DB row, only the update query is getting executed.
Please let me know why this kind of behaviour for delete operation. Isn't it a performance issue since two queries are getting hit rather than one?
Edit:
I am using hibernate 3.2.5
Sample code:
SessionFactory sessionFactory = new Configuration().configure("student.cfg.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
Student student = new Student();
student.setFirstName("AAA");
student.setLastName("BBB");
student.setCity("CCC");
student.setState("DDD");
student.setCountry("EEE");
student.setId("FFF");
session.delete(student);
session.flush();
session.close();
cfg.xml
<property name="hibernate.connection.username">system</property>
<property name="hibernate.connection.password">XXX</property>
<property name="hibernate.connection.driver_class">oracle.jdbc.OracleDriver</property>
<property name="hibernate.connection.url">jdbc:oracle:thin:#localhost:1521/orcl</property>
<property name="hibernate.jdbc.batch_size">30</property>
<property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
<property name="hibernate.cache.use_query_cache">false</property>
<property name="hibernate.cache.use_second_level_cache">false</property>
<property name="hibernate.connection.release_mode">after_transaction</property>
<property name="hibernate.connection.autocommit">true</property>
<property name="hibernate.connection.pool_size">0</property>
<property name="hibernate.current_session_context_class">thread</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
hbm.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.infy.model.Student" table="STUDENT">
<id name="id" column="ID">
<generator class="assigned"></generator>
</id>
<property name="firstName" type="string" column="FIRSTNAME"></property>
<property name="lastName" type="string" column="LASTNAME"></property>
<property name="city" type="string" column="CITY"></property>
<property name="state" type="string" column="STATE"></property>
<property name="country" type="string" column="COUNTRY"></property>
</class>
The reason is that for deleting an object, Hibernate requires that the object is in persistent state. Thus, Hibernate first fetches the object (SELECT) and then removes it (DELETE).
Why Hibernate needs to fetch the object first? The reason is that Hibernate interceptors might be enabled (http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/events.html), and the object must be passed through these interceptors to complete its lifecycle. If rows are delete directly in the database, the interceptor won't run.
On the other hand, it's possible to delete entities in one single SQL DELETE statement using bulk operations:
Query q = session.createQuery("delete Entity where id = X");
q.executeUpdate();
To understand this peculiar behavior of hibernate, it is important to understand a few hibernate concepts -
Hibernate Object States
Transient - An object is in transient status if it has been
instantiated and is still not associated with a Hibernate session.
Persistent - A persistent instance has a representation in the
database and an identifier value. It might just have been saved or
loaded, however, it is by definition in the scope of a Session.
Detached - A detached instance is an object that has been persistent,
but its Session has been closed.
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/objectstate.html#objectstate-overview
Transaction Write-Behind
The next thing to understand is 'Transaction Write behind'. When objects attached to a hibernate session are modified they are not immediately propagated to the database. Hibernate does this for at least two different reasons.
To perform batch inserts and updates.
To propagate only the last change. If an object is updated more than once, it still fires only one update statement.
http://learningviacode.blogspot.com/2012/02/write-behind-technique-in-hibernate.html
First Level Cache
Hibernate has something called 'First Level Cache'. Whenever you pass an object to save(), update() or saveOrUpdate(), and whenever you retrieve an object using load(), get(), list(), iterate() or scroll(), that object is added to the internal cache of the Session. This is where it tracks changes to various objects.
Hibernate Intercepters and Object Lifecycle Listeners -
The Interceptor interface and listener callbacks from the session to the application, allow the application to inspect and/or manipulate properties of a persistent object before it is saved, updated, deleted or loaded.
http://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html#d0e3069
This section Updated
Cascading
Hibernate allows applications to define cascade relationships between associations. For example, 'cascade-delete' from parent to child association will result in deletion of all children when a parent is deleted.
So, why are these important.
To be able to do transaction write-behind, to be able to track multiple changes to objects (object graphs) and to be able to execute lifecycle callbacks hibernate needs to know whether the object is transient/detached and it needs to have the object in it's first level cache before it makes any changes to the underlying object and associated relationships.
That's why hibernate (sometimes) issues a 'SELECT' statement to load the object (if it's not already loaded) in to it's first level cache before it makes changes to it.
Why does hibernate issue the 'SELECT' statement only sometimes?
Hibernate issues a 'SELECT' statement to determine what state the object is in. If the select statement returns an object, the object is in detached state and if it does not return an object, the object is in transient state.
Coming to your scenario -
Delete - The 'Delete' issued a SELECT statement because hibernate needs to know if the object exists in the database or not. If the object exists in the database, hibernate considers it as detached and then re-attches it to the session and processes delete lifecycle.
Update - Since you are explicitly calling 'Update' instead of 'SaveOrUpdate', hibernate blindly assumes that the object is in detached state, re-attaches the given object to the session first level cache and processes the update lifecycle. If it turns out that the object does not exist in the database contrary to hibernate's assumption, an exception is thrown when session flushes.
SaveOrUpdate - If you call 'SaveOrUpdate', hibernate has to determine the state of the object, so it uses a SELECT statement to determine if the object is in Transient/Detached state. If the object is in transient state, it processes the 'insert' lifecycle and if the object is in detached state, it processes the 'Update' lifecycle.
I'm not sure but:
If you call the delete method with a non transient object, this means first fetched the object from the DB. So it is normal to see a select statement. Perhaps in the end you see 2 select + 1 delete?
If you call the delete method with a transient object, then it is possible that you have a cascade="delete" or something similar which requires to retrieve first the object so that "nested actions" can be performed if it is required.
Edit:
Calling delete() with a transient instance means doing something like that:
MyEntity entity = new MyEntity();
entity.setId(1234);
session.delete(entity);
This will delete the row with id 1234, even if the object is a simple pojo not retrieved by Hibernate, not present in its session cache, not managed at all by Hibernate.
If you have an entity association Hibernate probably have to fetch the full entity so that it knows if the delete should be cascaded to associated entities.
instead of using
session.delete(object)
use
getHibernateTemplate().delete(object)
In both place for select query and also for delete use getHibernateTemplate()
In select query you have to use DetachedCriteria or Criteria
Example for select query
List<foo> fooList = new ArrayList<foo>();
DetachedCriteria queryCriteria = DetachedCriteria.forClass(foo.class);
queryCriteria.add(Restrictions.eq("Column_name",restriction));
fooList = getHibernateTemplate().findByCriteria(queryCriteria);
In hibernate avoid use of session,here I am not sure but problem occurs just because of session use

Categories

Resources