I'm trying to cache lazy loaded collections with ehcache/hibernate in a Spring project. When I execute a session.get(Parent.class, 123) and browse through the children multiple times a query is executed every time to fetch the children. The parent is only queried the first time and then resolved from the cache.
Probably I'm missing something, but I can't find the solution. Please see the relevant code below.
I'm using Spring (3.2.4.RELEASE) Hibernate(4.2.1.Final) and ehcache(2.6.6)
The parent class:
#Entity
#Table(name = "PARENT")
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")
public class Parent implements Serializable {
/** The Id. */
#Id
#Column(name = "ID")
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parent")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private List<Child> children;
public List<Child> getChildren() {
return children;
}
public void setChildren(List<Child> children) {
this.children = children;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Parent that = (Parent) o;
if (id != that.id) return false;
return true;
}
#Override
public int hashCode() {
return id;
}
}
The child class:
#Entity
#Table(name = "CHILD")
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE, include = "all")
public class Child {
#Id
#Column(name = "ID")
private int id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "PARENT_ID")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Parent parent;
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
private Parent getParent(){
return parent;
}
private void setParent(Parent parent) {
this.parent = parent;
}
#Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Child that = (Child) o;
return id == that.id;
}
#Override
public int hashCode() {
return id;
}
}
The application context:
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>Parent</value>
<value>Child</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.SQLServer2008Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
<prop key="hibernate.connection.charSet">UTF-8</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.use_sql_comments">true</prop>
<!-- cache settings ehcache-->
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class"> org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>
<prop key="hibernate.generate_statistics">true</prop>
<prop key="hibernate.cache.use_structured_entries">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.transaction.factory_class"> org.hibernate.engine.transaction.internal.jta.JtaTransactionFactory</prop>
<prop key="hibernate.transaction.jta.platform"> org.hibernate.service.jta.platform.internal.JBossStandAloneJtaPlatform</prop>
</props>
</property>
</bean>
The testcase I'm running:
#Test
public void testGetParentFromCache() {
for (int i = 0; i <3 ; i++ ) {
getEntity();
}
}
private void getEntity() {
Session sess = sessionFactory.openSession()
sess.setCacheMode(CacheMode.NORMAL);
Transaction t = sess.beginTransaction();
Parent p = (Parent) s.get(Parent.class, 123);
Assert.assertNotNull(p);
Assert.assertNotNull(p.getChildren().size());
t.commit();
sess.flush();
sess.clear();
sess.close();
}
In the logging I can see that the first time 2 queries are executed getting the parent and getting the children. Furthermore the logging shows that the child entities as well as the collection are stored in the 2nd level cache. However when reading the collection a query is executed to fetch the children on second and third attempt.
As EHCache was not working we tried infinispan as well (with different concurrency level). Unfortunately we keep experiencing the same problem.
P.S. This problem is also addressed in the EHCache forum: http://forums.terracotta.org/forums/posts/list/8785.page
and the hibernate forum: https://forum.hibernate.org/viewtopic.php?f=1&t=1029899
Furthermore my colleague created an example project resembling our project setup and problem at GitHub: https://github.com/basvanstratum/cacheimpl
The problem here lies within your version of Hibernate. The following code will explain what is going wrong here.
public class DefaultInitializeCollectionEventListener
implements InitializeCollectionEventListener {
public void onInitializeCollection(InitializeCollectionEvent event)
throws HibernateException {
…
final boolean traceEnabled = LOG.isTraceEnabled();
…
final boolean foundInCache = methodReturningTrueWhenInCache();
if ( foundInCache && traceEnabled ) {
LOG.trace( "Collection initialized from cache" );
}
else {
if ( traceEnabled ) {
LOG.trace( "Collection not cached" );
}
methodThatExecutesAQuery();
}
}
This is the link to Hibernate's JIRA ticket concerning this. The solution is to either enable trace logging (yuck - forget I even said this) or upgrade your libraries to version 4.2.2 or higher.
https://hibernate.atlassian.net/browse/HHH-8250
Related
i am trying to achieve second level caching while logging in user.
my scenario:-
whenever a user is validated it needs to pull more details from other tables. like in this case, i am pulling data from userdetails table using inner join.
each time a user is validated it pulls the same data. so i don't want to keep executing query every time a user tries to login. in this case maybe i can make a use of second level cache.
unfortunately i'm not able to achieve second level cache of hibernate here.
UserController
#Controller
public class UserController {
#Autowired
private UserDao userDao;
#RequestMapping("/getUserDetails")
public #ResponseBody List<UserDetailsMapping> userDetailsMappings(){
return userDao.getUserDetails();
}
}
UserDao
#Repository
public class UserDao {
#Autowired
private SessionFactory sessionFactory;
public List<UserDetailsMapping> getUserDetails(){
Session session = sessionFactory.openSession();
String sql ="select u.username,ud.address,ud.height,ud.weight from
User u inner join UserDetails ud \n" +
"on u.username=ud.username where u.username='sagar' and
u.password='sagar';";
Query query = session.createNativeQuery(sql,"userMapping")
.setCacheable(true);
List<UserDetailsMapping> list = query.getResultList();
return list;
}
}
User.class
#Entity
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class User {
#Id
private String username;
private String password;
**************constructors and getter setters**********
}
UserDetails.class
#Entity
#Cacheable
#Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
#SqlResultSetMapping(name = "userMapping",
classes = {#ConstructorResult(targetClass = UserDetailsMapping.class,
columns = {
#ColumnResult(name = "username",type = String.class),
#ColumnResult(name = "address",type = String.class),
#ColumnResult(name = "height",type = Integer.class),
#ColumnResult(name = "weight",type = Integer.class)
})
})
public class UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String username;
private String address;
private String gender;
private int height;
private int weight;
**************constructors and getter setters**********
}
applicationContext Configuration
<bean id="sessionFactory" class="org.springframework.orm.
hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">
org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>com.test</value>
</list>
</property>
</bean>
I know there are few threads about this, but I cannot understand why it is not working with my code. I have the entire database with capital letters, columns, table names, sequences.But when I try to make a query, via sql or criteria, It transforms all values in lowercase. I found a workaround but I don't want to write queries like:
select a."COLUMN_1", a."COLUMN_2" from schema."A" a
And mappings like:
#Entity(name = "`A`")
public class A implements Serializable {
#Column(name = "`COLUMN_1`")
private Integer column1;
#Column(name = "`COLUMN_2`")
private Integer column2;
}
I tried to follow some threads in stackoverflow implementing my own naming strategy, but it neither didn't work .
public class ModifiedImprovedNamingStrategy extends PhysicalNamingStrategyStandardImpl{
#Override
public final Identifier toPhysicalColumnName(final Identifier name, final JdbcEnvironment context) {
return new Identifier(addUnderscores(name.getText()), name.isQuoted());
}
/**
* Adds the underscores.
*
* #param name
* the name
* #return the string
*/
protected static String addUnderscores(final String name) {
final StringBuilder buf = new StringBuilder(name.replace('.', '_'));
for (int i = 1; i < (buf.length() - 1); i++) {
if (Character.isLowerCase(buf.charAt(i - 1))
&& Character.isUpperCase(buf.charAt(i))
&& Character.isLowerCase(buf.charAt(i + 1))) {
buf.insert(i++, '_');
}
}
return "`" + buf.toString().toUpperCase(Locale.ROOT) + "`";
}
}
And then calling it in my applicationContext like that:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"
p:dataSource-ref="dataSource">
<property name="packagesToScan" value="com.services.vo"/>
<property name="mappingLocations">
<list>
<value>classpath*:hibernate/queries/**.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.transaction.coordinator_class">org.hibernate.transaction.JDBCTransactionFactory</prop>
<prop key="hibernate.physical_naming_strategy">com.services.util.hibernate.ModifiedImprovedNamingStrategy</prop>
<prop key="hibernate.format_sql">true</prop>
</props>
</property>
</bean>
My intention is to avoid writing those everywhere. I tried to set a breakpoint inside the overrided ModifiedImprovedNamingStrategy methods. When I try a unit test, but it is not stopping there.Is there any way to do what I want? or will I be forced to keep those ?
Thanks in advance
I believe you have to add annotation #Table(name = "A") in your case to mapping your Entity A into database table A , Here's my how i use it, hope it helps:
#Entity
#Table(name = "house")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "house")
public class House implements Serializable {
#NotNull
#Column(name = "location", nullable = false)
private String location;
#Size(max = 200)
#Column(name = "description", length = 200)
private String description;
}
My problem is that hibernate retrieve null in the value of the #OneToMany Set organizationMemberCollection when fetching an instance on the following object :
UserAccount.java :
#Entity
#Table(name="USER_ACCOUNT")
public class UserAccount {
#Id
#Column(name = "id", nullable = false)
#SequenceGenerator(name = "generator", sequenceName = "USER_ACCOUNT_id_seq", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
private Long id;
#Column(name = "EMAIL", nullable = false)
private String email;
#Column(name = "PASSWORD_HASH")
private String passwordHash;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount")
private Set <OrganizationMember> organizationMemberCollection;
...
/*
* getters and setters
*/
}
Here is the Object that "owns" the association :
OrganizationMember.java :
#Entity
#Table(name="ORGANIZATION_MEMBER")
public class OrganizationMember{
#Id
#Column(name = "id", nullable = false)
#SequenceGenerator(name = "generator", sequenceName = "ORGANIZATION_MEMBER_id_seq", allocationSize=1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ACCOUNT_ID", nullable = false)
private UserAccount userAccount;
...
/*
* getters and setters
*/
}
In this application we have two different configuations :
Production, where Hibernate is connected to a PostgreSQL database.
Here is the sessionFactory configuration for prod :
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.jdbc.batch_size">10</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
</props>
</property>
...
</bean>
Test, where Hibernate is conencted to an in memory HSQLDB database :
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.cglib.use_reflection_optimizer">false</prop>
<prop key="hibernate.hbm2ddl.auto">create-drop</prop>
<prop key="hibernate.cache.use_second_level_cache">false</prop>
<prop key="hibernate.cache.use_query_cache">false</prop>
</props>
</property>
...
</bean>
This issue only show up in testing configuration; In production configuration everything's going nicely and I can get the collection.
However, when I fetch an UserAccount in the test configuration I get null in the organizationMemberCollection property (Not an empty Set).
After some hours of research through google and Hibernate's doc I still haven't found any post relating to the same issue/behaviour, so I'm a lillte bit lost and help would be greatly appreciated !
I can of course provide more information if needed, Thanks !
Edit :
Test higlighting the problem :
#Test
#Transactional
public void testFindUserAccount_OrganizationMemberCollectionFetching() {
assertNotNull(userAccountDao.findUserAccount("user1#test.fr")); //NoProblem
assertNotNull(userAccountDao.findUserAccount("user1#test.fr").getCabinetMemberCollection()); //Fails
}
With the following findUserAccount dao
public UserAccount findUserAccount(String email) {
if (email == null) {
return null;
}
UserAccount userAccount = (UserAccount) this.sessionFactory
.getCurrentSession().createCriteria(UserAccount.class)
.add(Restrictions.eq("email", email).ignoreCase())
.uniqueResult();
if (userAccount == null) {
throw new ObjectNotFoundException("UserAccount.notFound");
} else {
return userAccount;
}
}
The issue was that the database population and the test were running in the same transaction, and hibernate cache wasn't cleaned between these two steps.
The consequence was that hibernate didn't really fired the request to the database, but hit the cache instead and returned the object without doing any join with the mapped relation.
The possible solutions are :
Populate the database in a different Transaction.
Clean the Session SessionFactory.getCurrentSession().clean(); after the population. (Flush the session before if needed : SessionFactory.getCurrentSession().flush();).
Each possibility will force the next query to really hit the database, therefore the join will occur and the mapped Collection will contain the wanted data (or be empty if the join has no result, but in any case the Collection won't have the null value).
In my opinin the first solution is way better as it doesn't rollback the whole population if something goes wrong in the test.
It's a lazy loaded collection, so hibernate doesn't do anything to initialize it, quite normal that hibernate returns null here ..
What I usually do is declare an empty HashSet on the property :
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userAccount")
private Set <OrganizationMember> organizationMemberCollection = new hashSet<>();
In my case it was because I had "transient" as the entity was not serializable an Sonar told me to add the transient keyword
#OneToMany(mappedBy = "message", cascade = CascadeType.REMOVE, orphanRemoval = true, fetch = FetchType.LAZY)
private transient List<UserReadSystemMessage> usersRead;
Note the transient
Just started learning hibernate, and to understand hibernate 2nd level cache. I created a comment Entity and try to work on it. Here are my codes!
#Entity
#Table(name = "comment")
#FilterDefs(value={#FilterDef(name="projectFilter",parameters=#ParamDef(name="projectID", type="java.lang.Long" )), #FilterDef(name="issueFilter", parameters=#ParamDef( name="issueID", type="java.lang.Long" ) )})
#Filters(value={#Filter(name = "projectFilter", condition = "project_id = :projectID"),
#Filter(name = "issueFilter", condition = "issue_id = :issueID")})
#Cache(usage=CacheConcurrencyStrategy.READ_WRITE, region="comment")
public class Comment {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false, precision = 5, scale = 0 )
private long id;
#Column(name = "project_id", nullable = false)
private long projectId;
#Column(name = "issue_id", nullable = false)
private long issueId;
#Column(name = "author_id", nullable = false)
private long auhorId;
#Column(name = "author_name", nullable = false)
private String authorName;
#Column(name = "comment")
private String comment;
#Column(name = "created_date")
private Date createdDate;
I want to fetch user comment based on project or project and issue. In DAO,I have written the following function for comments based on projectid.
#Autowired
private SessionFactory sessionFactory;
...
#Override
public List<Comment> getAllProjectComments(long projectId) {
Session session = getCurrentSession();
Filter filter = session.enableFilter("projectFilter");
filter.setParameter("projectID", projectId);
return session.createQuery("from Comment").setCacheable(true).list();
}
and ehcahe.xml is below
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd" updateCheck="false">
<diskStore path="java.io.tmpdir/hibernate-cache"/>
<defaultCache maxElementsInMemory="500"
eternal="false"
timeToIdleSeconds="1200"
timeToLiveSeconds="2400"
overflowToDisk="false"
maxElementsOnDisk="1000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="1200"
memoryStoreEvictionPolicy="LRU"/>
<cache name="org.trackMyProject.entity.Comment"
maxElementsInMemory="50"
maxElementsOnDisk="500"
eternal="false"
timeToIdleSeconds="30"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
I have added 500 comments in table for 2 projectids.
when the controller call DAO method for getting the comments based on the project id and subsequent call for that project id seems to work perfectly with 2nd level cache and DB is not getting hit. But if I switch between 2 projectIDs constantly, then each time DB gets hit, that I don't want.
Can anybody tell me what kind of mistake I have made or need to do more configuration.
Thanks in advance!!
EDIT!
classpath:hibernate.cfg.xml
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${jdbc.dialect}</prop>
<prop key="hibernate.show_sql">true</prop>
<!-- <prop key="hibernate.cache.use_query_cache">true</prop> -->
<prop key="hibernate.connection.release_mode">after_transaction</prop>
<prop key="hibernate.bytecode.use_reflection_optimizer">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<prop key="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
</props>
</property>
</bean>
You are using createQuery which would mean you need query level caching, try adding this :
<property name='hibernate.cache.use_query_cache'>true</property>
The below is the hibernate.cfg.xml:
<?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.dialect">org.hibernate.dialect.DerbyDialect</property>
<property name="hibernate.connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
<property name="hibernate.connection.url">jdbc:derby://localhost:1527/XE</property>
<property name="hibernate.connection.username">username</property>
<property name="hibernate.connection.password">password</property>
</session-factory>
</hibernate-configuration>
I wonder if it's always necessary to use hibernate.cfg.xml in every Hibernate Application or there is any alternative way to configure Hibernate.
You can do this by setting the properties using java
public class TestHibernate {
public static void main(String arg[]) {
Properties prop= new Properties();
prop.setProperty("hibernate.connection.url", "jdbc:mysql://<your-host>:<your-port>/<your-dbname>");
//You can use any database you want, I had it configured for Postgres
prop.setProperty("dialect", "org.hibernate.dialect.PostgresSQL");
prop.setProperty("hibernate.connection.username", "<your-user>");
prop.setProperty("hibernate.connection.password", "<your-password>");
prop.setProperty("hibernate.connection.driver_class", "org.postgresql.Driver");
prop.setProperty("show_sql", true); //If you wish to see the generated sql query
SessionFactory sessionFactory = new Configuration().addProperties(prop).buildSessionFactory();
Session session = sessionFactory.openSession();
session.beginTransaction();
Customer user = new Customer(); //Note customer is a POJO maps to the customer table in the database.
user.setName("test");
user.setisActive(true);
session.save(user);
session.getTransaction().commit();
session.close();
}
}
#Entity
#Table(name = "customer", uniqueConstraints = {
#UniqueConstraint(columnNames = "customerid")})
public class Customer implements Serializable{
private String name;
private int customerid;
private boolean isActive;
public Customer() {
}
public Customer(String name, int customerId, boolean isActive) {
this.name = name;
this.customerid = customerId;
this.isActive = isActive;
}
/**
* GETTERS
*/
#Column(name = "name", unique = false, nullable = false, length = 100)
public String getname() {
return name;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "customerid", unique = true, nullable = false)
public int getcustomerid() {
return customerid;
}
#Column(name = "isactive", unique = false, nullable = false)
public boolean getisactive() {
return isActive;
}
/**
* SETTERS
*/
public void setname(String name) {
this.name = name;
}
public void setisactive(boolean isActive) {
this.isActive = isActive;
}
}
It is not necessary, in the session factory bean configuration you can pass these values directly using
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop>
<prop key="hibernate.show_sql"></prop>
<prop key="hibernate.use_outer_join">true</prop>
</props>
</property>
ex
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.DerbyDialect</prop>
<prop key="hibernate.show_sql"></prop>
<prop key="hibernate.use_outer_join">true</prop>
<prop key="hibernate.jdbc.batch_size" >30</prop>
<prop key="hibernate.connection.SetBigStringTryClob">true</prop>
</props>
</property>
<property name="packagesToScan">
<list>
<value>mypackage</value>
</list>
</property>
</bean>
You can Specify the properties of hibernate.cfg.xml as property injection in spring bean.xml
for example
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
So in similar way you can specify all properties in spring as a dependency for sessionFacory