Hibernate retrieve null for OneToMany collection - java

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

Related

how to achieve hibernate second level cache on the query with joins?

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>

Hibernate 5 naming strategy for postgresql

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;
}

hibernate 2nd level cache understanding

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>

How to refresh updated entity data without restarting the server

I am using EclipseLink JPA as ORM and web logic 10.3 as an application server in my project. Everything working fine until i got a bug for data refresh. Here is the case one of my entity table row is updated to new value but my entity manager or JPA did not pick that value. For this we have lite rely re started the server. then it picked up the value.
Here is my persistence.xml file and here is the way i am using entity manager in my class.
<persistence-unit name="BasePersistenceUnit" transaction-type="JTA">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<jta-data-source>jdbc/CTH_DS</jta-data-source>
<class>org.test.partyrequest.model.dataobject.RqstTrc</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="eclipselink.target-server" value="WebLogic_10" />
<!-- Logging level is set to INFO, Need to change in Production -->
<property name="eclipselink.logging.level" value="FINE" />
<property name="eclipselink.persistence-context.flush-mode" value="COMMIT" />
<property name="eclipselink.persistence-context.close-on-commit" value="true" />
<property name="eclipselink.cache.shared.default" value="false" />
</properties>
</persistence-unit>
SPRING JPA XML FILE
<context:load-time-weaver aspectj-weaving="on" />
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitName" value="BasePersistenceUnit" />
</bean>
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter" />
<bean id="transactionManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager" />
<!-- ========================= BUSINESS OBJECT DEFINITIONS ========================= -->
<!-- Instruct Spring to perform declarative transaction management automatically on annotated classes. -->
<tx:annotation-driven mode="aspectj" transaction-manager="transactionManager"/>
<!-- Post-processor to perform exception translation on #Repository classes
(from native exceptions such as JPA PersistenceExceptions to Spring's DataAccessException hierarchy).
-->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
My Entity calss
#Entity
#Table(name = "PRTY_RQST")
public class PrtyRqst implements Serializable {
private static final long serialVersionUID = -4679712398918736694L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PRTY_RQST_PRTYRQSTID_GENERATOR")
#SequenceGenerator(name = "PRTY_RQST_PRTYRQSTID_GENERATOR", allocationSize = 1, sequenceName = "PRTY_RQST_SEQ")
#Column(name = "PRTY_RQST_ID")
private Long prtyRqstId;
#Column(name = "CHLD_RQST_IND")
private String chldRqstInd;
#Column(name = "PARNT_PRTY_RQST_ID")
private BigDecimal parntPrtyRqstId;
#Column(name = "PROCES_REFR")
private String procesRefr;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "RQST_DT_TM")
private Date rqstDtTm;
#Column(name = "UPDT_BY")
private String updtBy;
// bi-directional many-to-one association to PrtyKey
#OneToMany(mappedBy = "prtyRqst", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<PrtyKey> prtyKeys;
// bi-directional many-to-one association to PrtyRqstHist
#OneToMany(mappedBy = "prtyRqst", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#OrderBy("rqstDtTm DESC")
private List<PrtyRqstHist> prtyRqstHists;
#OneToOne(mappedBy = "prtyRqst", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private RqstPayload rqstPayload;
// bi-directional many-to-one association to RqstTrc
#OneToMany(mappedBy = "prtyRqst", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<RqstTrc> rqstTrcs;
// bi-directional many-to-one association to AddtnRqstInfo
#OneToMany(mappedBy = "prtyRqst", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<AddtnRqstInfo> addtnRqstInfos;
// bi-directional many-to-one association to BusnApplc
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "BUSN_APPLC_ID")
private BusnApplc busnApplc;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "INTN_PROCES_TYP_ID")
private IntnProcesTyp intnProcesTyp;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "INTN_STATS_ID")
private IntnStat intnStat;
#Column(name = "ORCHESTRATION_ID")
private String orchestrationId;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "ALLW_CHNL_ID")
private AllwChnl allwChnl;
// bi-directional many-to-one association to RqstTyp
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumn(name = "RQST_TYP_ID")
private RqstTyp rqstTyp;
#Column(name = "TRACK_RQST_IND")
private String trackRqstInd;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "SUBMIT_DT_TM")
private Date submitDtTm;
#Temporal(TemporalType.DATE)
#Column(name = "EFFECTIVE_DT")
private Date effectiveDt;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "RQST_CREATE_DT_TM")
private Date rqstCreateDtTm;
In my DAO IMPL class I have this.persist(prtyRqstDO);
#Transactional(readOnly = true, propagation=Propagation.REQUIRED)
private PartyRequestBO createRequest(PartyRequestBO partyRequestBO, boolean isParent) throws RuntimeException {
if (log.isDebugEnabled()) {
log.debug("Enter: PartyRequestsDAOImpl:createRequest()");
}
partyRequestBO.setOrchestrationID(generateOrchestrationId());
PrtyRqst prtyRqstDO = PartyRequestEntityMapper.partyRequestMapper(partyRequestBO, isParent, true);
try {
this.persist(prtyRqstDO);
partyRequestBO.setRequestIdentifier(prtyRqstDO.getPrtyRqstId());
} catch (Exception e) {
if(log.isDebugEnabled()) {
log.debug("PartyRequestsDAOImpl:createRequest : " + PartyRequestConstants.UNABLE_TO_INSERT, e);
}
throw new PartyRequestDataException(PartyRequestConstants.UNABLE_TO_INSERT, e);
}
if (log.isDebugEnabled()) {
log.debug("Exit: PartyRequestsDAOImpl:createRequest()");
}
return partyRequestBO;
}
#Transactional(readOnly = true, propagation=Propagation.REQUIRED)
public void persist(T entity) {
if (log.isDebugEnabled()) {
log.debug("Enter: BaseDAO:persist() : " + entity);
}
this.getEntityManager().persist(entity);
if (log.isDebugEnabled()) {
log.debug("Exit: BaseDAO:persist()");
}
}
public EntityManager getEntityManager() {
if (log.isDebugEnabled()) {
log.debug("Enter: BaseDAO:getEntityManager() : " + this.entityManager);
}
return this.entityManager;
}
Here the problem is, if i update the row in one of the table through back end my application container is not picking the change.
Can any one Tell me? thank you in advance.
EDIT:
Thank you both of you. I have modified according to your comments added following lines of code.
this.entityManager.clear();
this.entityManager.close();
//this.getEntityManager().refresh(entityManager);
Here i could able to get the update value what i have done it through backend with out restarting server. But the problem is it hold all the changed values.
for example i have changed value to FulOrderWSA it was working. changed to FulorderWSB it was working again.Now i have tried for FulOrderWSZ it didn't work(DB values is FulorderWSB ).
Finally i tried here with old value that is FulorderWSA as per DB it should not work but it worked for me. what i noticed that it is holding all the DB changed values here.
How to get ride of this. I have used both clear and close for entityManager. can any one help me on this.
thank you.
Vijay.
You have turned off the EclipseLink shared cache (AKA second level cache), so the issue is likely that you are holding onto long lived EntityManagers. Once an entity becomes managed by the EM, JPA requires that EM to return that exact instance from every find/query operation as it was when first read and any changes your app might have made to it.
There are a number of options. The best is to look at your EntityManager lifecycle and only obtain an EntityManager when needed, and close it when done. Or, just call em.clear() at points to prevent them from filling up, which will detach all entities associated to the em. Make sure to flush changes though if you wish to keep the changes before calling clear.
If there is a specific entity you need to refresh, em.refresh(entity) will work. This will clear any changes the application might have made though, and can be dangerous with cascade refresh settings mixed with lazy access - so use carefully or you may unintentionally wipe out changes to a whole tree at a later time.
You have caching disabled, so you should see any database changes.
My guess is that you are hold onto a single EntityManager in your DAO. This is very bad, as an EntityManger should be created per transaction, or per request, not held for the duration of the application. It is also not thread safe, so holding onto a single one does not make sense, as it is a transactional object.
You seem to also be using Spring, so it might be proxying the EntityManager underneath and creating one per transaction, but perhaps you have not configured Spring or your transactions correctly.
Include the code the creating/configures the EntityManager.
Thanks,
for all your support. Basically we are not using Cache from EclipseLink. we had bean that handles all the metadata initialization as init method. what we have done is, used JDK Timer to reload the particular Bean to refresh the data. It was working fine.
I have checked the time taking to refresh the all the methods are less than 500 milliseconds. I can foresee only issue when this thread is executing and there is a request.since it is taking less than 500 millisecs its ok for us.
I hope this will be helpful if someone is not using cache you can try this approach.
Thank you.

Spring, Hibernate, Blob lazy loading

I need help with lazy blob loading in Hibernate.
I have in my web application these servers and frameworks: MySQL, Tomcat, Spring and Hibernate.
The part of database config.
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="initialPoolSize">
<value>${jdbc.initialPoolSize}</value>
</property>
<property name="minPoolSize">
<value>${jdbc.minPoolSize}</value>
</property>
<property name="maxPoolSize">
<value>${jdbc.maxPoolSize}</value>
</property>
<property name="acquireRetryAttempts">
<value>${jdbc.acquireRetryAttempts}</value>
</property>
<property name="acquireIncrement">
<value>${jdbc.acquireIncrement}</value>
</property>
<property name="idleConnectionTestPeriod">
<value>${jdbc.idleConnectionTestPeriod}</value>
</property>
<property name="maxIdleTime">
<value>${jdbc.maxIdleTime}</value>
</property>
<property name="maxConnectionAge">
<value>${jdbc.maxConnectionAge}</value>
</property>
<property name="preferredTestQuery">
<value>${jdbc.preferredTestQuery}</value>
</property>
<property name="testConnectionOnCheckin">
<value>${jdbc.testConnectionOnCheckin}</value>
</property>
</bean>
<bean id="lobHandler" class="org.springframework.jdbc.support.lob.DefaultLobHandler" />
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="configLocation" value="/WEB-INF/hibernate.cfg.xml" />
<property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
</props>
</property>
<property name="lobHandler" ref="lobHandler" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
The part of entity class
#Lob
#Basic(fetch=FetchType.LAZY)
#Column(name = "BlobField", columnDefinition = "LONGBLOB")
#Type(type = "org.springframework.orm.hibernate3.support.BlobByteArrayType")
private byte[] blobField;
The problem description. I'm trying to display on a web page database records related to files, which was saved in MySQL database. All works fine if a volume of data is small. But the volume of data is big I'm recieving an error java.lang.OutOfMemoryError: Java heap space
I've tried to write in blobFields null values on each row of table. In this case, application works fine, memory doesn't go out of. I have a conclusion that the blob field which is marked as lazy (#Basic(fetch=FetchType.LAZY)) isn't lazy, actually!
I'm confused. Emmanuel Bernard wrote in ANN-418 that #Lob are lazy by default (i.e. you don't even need to use the #Basic(fetch = FetchType.LAZY) annotation).
Some users report that lazy loading of a #Lob doesn't work with all drivers/database.
Some users report that it works when using bytecode instrumentation (javassit? cglib?).
But I can't find any clear reference of all this in the documentation.
At the end, the recommended workaround is to use a "fake" one-to-one mappings instead of properties. Remove the LOB fields from your existing class, create new classes referring to the same table, same primary key, and only the necessary LOB fields as properties. Specify the mappings as one-to-one, fetch="select", lazy="true". So long as your parent object is still in your session, you should get exactly what you want. (just transpose this to annotations).
I would suggest you to use inheritance to handle this scenario. Have a base class without the blob and a derived class containing the byte array. You would use the derived class only when you need to display the blob on the UI.
Of course you could extract that value and put it into a new table with a "#OneToOne" relation that is lazy, however in our application the LOBs are loaded lazily on demand using just this configuration
#Lob
#Fetch(FetchMode.SELECT)
#Type(type="org.hibernate.type.PrimitiveByteArrayBlobType")
byte[] myBlob;
This is tested in our project simultaneously on PostgreSQL, MySQL, SQLServer and Oracle, so it should work for u
Lazy property loading requires buildtime bytecode instrumentation.
Hibernate docs: Using lazy property fetching
If you want to avoid bytecode instrumentation one option is to to create two entities that use same table, one with the blob one without. Then only use the entity with blob when you need the blob.
I had the same issue and this was my fix:
My Entity:
#Entity
#Table(name = "file")
public class FileEntity {
#Id
#GeneratedValue
private UUID id;
#NotNull
private String filename;
#NotNull
#Lob #Basic(fetch = FetchType.LAZY)
private byte[] content;
...
Added plugin to pom.xml:
<plugin>
<groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId>
<executions>
<execution>
<phase>compile</phase>
<configuration>
<failOnError>true</failOnError>
<enableLazyInitialization>true</enableLazyInitialization>
</configuration>
<goals>
<goal>enhance</goal>
</goals>
</execution>
</executions>
</plugin>
For me lazy load only worked by compiling and then running it, didn't work on eclipse or intellij for example.
I'm using gradle then I did the following to get it working
Annotate entity
Setup Hibernate gradle plugin
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "org.hibernate:hibernate-gradle-plugin:5.4.0.Final"
}
}
apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'org.hibernate.orm'
hibernate {
enhance {
enableLazyInitialization = true
enableDirtyTracking = true
enableAssociationManagement = true
}
}
Entity.java
#Entity
public class Person {
#Id
#GeneratedValue
private Integer id;
#Lob
#Basic(fetch = FetchType.LAZY)
#Column(length = 255, nullable = false)
private String name;
Testing
./gradlew run
Full working example
Lazy loading works for me if I use Blob type instead of byte[].
#Column(name = "BlobField", nullable = false)
#Lob
#Basic(fetch = FetchType.LAZY)
private Blob blobField;
This one gets lazily loaded and if you need to retrieve its value access this field:
String value = IOUtils.toByteArray(entity.getBlobField().getBinaryStream());
A simple workarround using #OneTone notation based on the response of #MohammadReza Alagheband (Why does #Basic(fetch=lazy) doesn't work in my case?) but without the requirement of create a new table for each required lazy attribute is the following:
#Getter
#Setter
#Entity
#Table(name = "document")
#AllArgsConstructor
#NoArgsConstructor
public class DocumentBody implements java.io.Serializable{
#Column(name = "id", insertable = false)
#ReadOnlyProperty
#Id
#PrimaryKeyJoinColumn
private Integer id;
#Column(name = "body", unique = true, nullable = false, length = 254)
#JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
private String content;
}
#Getter
#Entity
#Setter
#Table(name = "document")
#AllArgsConstructor
#NoArgsConstructor
public class DocumentTitle implements java.io.Serializable{
#Column(name = "id", insertable = false)
#ReadOnlyProperty
#Id
private Integer id;
#Column(name = "title", unique = true, nullable = false, length = 254)
#JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
private String content;
}
public class Document implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
#JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
private Integer id;
//Also it is posssible to prove with #ManyToOne
#OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
#JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
private DocumentTitle title;
#OneToOne(fetch = FetchType.LAZY, optional = false, cascade = {CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
#JsonView({JSONViews.Simple.class, JSONViews.Complete.class})
private DocumentBody body;
}

Categories

Resources