We need to audit the existing table using envers. we don't have hibernate.xml instead of we are using application-context.xml. And we are creating schema through "liquibase-changeset", then how do I create through annotations like #Entity and #Audited.
How do I solve this issue?
I have added hibernate configuration likes
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
<prop key="hibernate.ejb.event.post-insert">org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener</prop>
<prop key="hibernate.ejb.event.post-update">org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener</prop>
<prop key="hibernate.ejb.event.post-delete">org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener</prop>
<prop key="hibernate.ejb.event.pre-collection-update">org.hibernate.envers.event.AuditEventListener</prop>
<!-- <prop key="hibernate.ejb.event.pre-collection-remove">org.hibernate.envers.event.AuditEventListener</prop>
<prop key="hibernate.ejb.event.post-collection-recreate">org.hibernate.envers.event.AuditEventListener</prop> -->
<prop key="org.hibernate.envers.revision_field_name">REV</prop>
<prop key="org.hibernate.envers.revision_type_field_name">REVTYPE</prop>
<prop key="org.hibernate.envers.auditTablePrefix"></prop>
<prop key="org.hibernate.envers.auditTableSuffix">_HISTORY</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
Added #Audited annotation in my domain class
#Entity
#Audited
#Table(name="user")
public class User implements Serializable {
But this configuration deleted my existing tables
e.g
Mydatabase
-----------
user
product
order_details
user_role
login
I have 5 tables in my database. After running my application it displays 3 tables. Instead of creating "audit" table, it deletes the existing table.
Mydatabase
-----------
user
product
order_details
How to create audit(_HISTORY) table without touching existing tables???
In the Liquibase changeset define the audit table definition like you would for any other table.
Skip the hibernate.hbm2ddl.auto property in spring-hibernate ocnfiguration.That will instruct hibernate to not do anything to the schema.
Keeping rest of your configuartion as it is, this should work.
Just ensure the audit tables names in schema and in configuration match.
Link to doc detailing how its done in case schema is generated using ant
I was facing the same issue, to resolve it, I followed the next steps:
change :
<prop key="hibernate.hbm2ddl.auto">create</prop>
to:
<prop key="hibernate.hbm2ddl.auto">update</prop>
If you work with ENVERS Hibernet-envers 3.5.5 or + you should have this configuration in your application-context:
<property name="eventListeners">
<map>
<entry key="post-insert" >
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
<entry key="post-update">
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
<entry key="post-delete">
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
<entry key="pre-collection-update">
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
<entry key="pre-collection-remove">
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
<entry key="post-collection-recreate">
<bean class="org.hibernate.envers.event.AuditEventListener" />
</entry>
</map>
</property>
You have to define a revision entity like this one:
#Entity
#Table(name = "MY_REVINFO")
#RevisionEntity(MyRevisionListener.class)//#see next class
public class MyRevisionEntity {
private static final long serialVersionUID =1L;
#Id
#GeneratedValue
#RevisionNumber
private int id;
#RevisionTimestamp
private long timestamp;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
#Transient
public Date getRevisionDate() {
return new Date(timestamp);
}
#Column(name = "USER_NAME")
private String userName;
#Column(name = "DATE_OPER")
private Date dateOperation;
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof DefaultRevisionEntity)) return false;
DefaultRevisionEntity that = (DefaultRevisionEntity) o;
if (id != that.getId()) return false;
if (timestamp != that.getTimestamp()) return false;
return true;
}
public int hashCode() {
int result;
result = id;
result = 31 * result + (int) (timestamp ^ (timestamp >>> 32));
return result;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getDateOperation() {
return dateOperation;
}
public void setDateOperation(Date dateOperation) {
this.dateOperation = dateOperation;
}
public String toString() {
return "DefaultRevisionEntity(id = " + id + ", revisionDate = " + DateFormat.getDateTimeInstance().format(getRevisionDate()) + ")";
}
}
Add the mapping of this new entity in your application-context.xml as :
<value>mypackage.MyRevisionEntity</value>
Create the listener (it's very helpfull if you want to save the user name and the operation time):
public class MyRevisionListener implements RevisionListener {
public void newRevision(Object revisionEntity) {
MyRevisionEntity revision = (MyRevisionEntity) revisionEntity;
SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String userName="---";
if (userDetails != null) {
userName=userDetails.getUsername();
} else {
userName="UNKNOWN";
}
revision.setUserName(userName);
revision.setDateOperation(new Date(revision.getTimestamp()));
}
}
Clean, install and run your application.
If the problem persists try to upgrade your Envers version (Hibrenate-envers and Hibernate-core)
Hope this help.
Try changing the DDL strategy from:
<prop key="hibernate.hbm2ddl.auto">create</prop>
to:
<prop key="hibernate.hbm2ddl.auto">update</prop>
The update DDL generation strategy shouldn't delete any existing table.
I'm working on a project using JPA with Hibernate implementation and we managed to make envers work without many problems using Spring context xml based configuration only (we neither use persistence.xml).
Our configuration is bassed on Spring JPA support but it's possible that you can find a similar solution:
<bean id="projectEntityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
</bean>
</property>
<property name="packagesToScan">
<list>
<value>com.teimas.myproject.bo</value>
<value>com.teimas.myproject.bo.commons</value>
<value>com.teimas.myproject.bo.util</value>
</list>
</property>
<property name="persistenceUnitName" value="projectPU" />
<property name="jtaDataSource" ref="projectDataSourceTarget" />
<!-- Other ptops for hibernate config -->
<property name="jpaProperties" ref="jpaHibernateProperties" />
</bean>
<util:properties id="jpaHibernateProperties">
<prop key="hibernate.transaction.jta.platform">
org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform
</prop>
<prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
<!-- validate | update | create | create-drop -->
<prop key="hibernate.hbm2ddl.auto">validate</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.format_sql">false</prop>
<prop key="javax.persistence.transactionType">JTA</prop>
<prop key="javax.persistence.validation.mode">AUTO</prop>
</util:properties>
The key is that we use as JPA provider a hibernate object: org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter and we add packagesToScan property to tell hibernate to scan for annotations in those packages. So Hibernate find Envers and Validation anotations, and all work fine.
Hope this helps.
In DAO:
private Map<Integer,String> departments = new LinkedHashMap<Integer, String>();
#Override
public List<DepartmentEntity> getAllDepartments() {
return this.sessionFactory.getCurrentSession().createQuery("from DepartmentEntity de order by LOWER(de.departmentname)").list();
}
#Override
public Map<Integer, String> loadDepartments() {
departments.clear();
for (DepartmentEntity de : getAllDepartments())
departments.put(de.getDepartmentid(), de.getDepartmentname());
return departments;
}
Its Working fine, but in spring creation of objects manually its bad code
private Map<Integer,String> departments;
So, how to inject map object of LinkedHashMap type from out side in my case ?.
I tried but i got exceptions like null pointer exception
Please any one help me..
<util:map id="myMap" map-class="java.util.LinkedHashMap" key-type="java.lang.Integer" value-type="java.lang.String"/>
<bean id="departmentDAOImpl" class="com.leadwinner.infra.assets.dao.DepartmentDAOImpl">
<property name="departments" ref="myMap"></property>
</bean>
You can do something like below:
eg.
class A{
private B b;
public setB(B b){
this.b = b;
}
public Map getMapFromA(){
return b.getMap();
}
}
class B{
private Map tmp;
public void setMap(HashMap t){
tmp.putAll(t);
}
public HashMap getMap(){
return tmp;
}
}
And in web.xml
<bean id="classB" class="default.B"/>
<bean id ="classA" class="default.A"/>
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject"><ref local="classA"></property>
<property name="targetMethod"><value>setB</value></property>
<property name="arguments"><ref local="classB"/></property>
</bean>
Now spring beans are by default singleton scoped. So you can do the following.
function do(){
B b = ctx.getBean("classB");
b.setMap(someMap);
A a = ctx.getBean("classA");
a.getMapFromA();
}
I havent tried out the code but it will give you an idea I hope so. More details on MethodInvokingFactoryBean : here
And if you dont want to do it by Spring and if you want less efforts try using ThreadLocal to pass parameters.
Populate map in this way (using constructor injection):
<bean name="DAO" class="path.to.yourDAOClass">
<constructor-arg index="0">
<map>
<entry key="1" value="One" />
<entry key="2" value="Two" />
</map>
</constructor-arg>
<bean>
By default target class for <map /> is a LinkedHashMap, but you can change target class using a MapFactoryBean to construct your map object in this way by replace the <map /> tag with:
<bean class="org.springframework.beans.factory.config.MapFactoryBean">
<property name="targetMapClass">
<value>java.util.HashMap</value>
</property>
<property name="sourceMap">
<map>
<entry key="1" value="One" />
<entry key="2" value="Two" />
</map>
</property>
</bean>
I'm having problems trying to use batchUpdate, from Spring's JdbcTemplate.
The problem is that i want to execute two SQL operations: a DELETE method (to clear my table) and then an INSERT method. It works fine the first time i make the call (from jsp). But from the second attempt on, when i try to do the call, the DELETE procedure isn't called or executed, just the INSERT procedure, causing an unique constraint exception.
First i tried this:
public class MyTableDAOStoredProcedure extends JdbcDaoSupport implements MyTableDAO {
...
....
public void insert(final List<MyObject> myObjectList) {
...
String deleteSql = "DELETE FROM ......";
String insertSql = "INSERT INTO ......";
// Delete Procedure
jdbcTemplate.execute(deleteSql);
//Insert Procedure
jdbcTemplate.batchUpdate(insertSql, new BatchPreparedStatementSetter() {
#Override
public int getBatchSize() {
return myObjectList.size();
}
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
MyObject object = myObjectList.get(i);
ps.setString(1, myObject.getA());
ps.setInt(2, myObject.getB());
}
});
}
}
Then i tried this:
public class MyTableDAOStoredProcedure extends JdbcDaoSupport implements MyTableDAO {
...
...
String deleteSql = "DELETE FROM MY OBJECT";
String insertsql = "INSERT INTO MY_OBJECT values(1,2)";
jdbcTemplate.batchUpdate(new String[] { deleteSql, insertSql});
}
I think it might be some Spring Transaction problem. Here it is the config of my DAO procedure on applicationContext.xml, it's quite simple:
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
.
.
.
<bean id="myTableDAOStoredProcedure" class="....dao.spring.MyTableDAOStoredProcedure">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
Any ideas or suggestions?
pm.detachCopy is working?
I'm making a Spring + ObjectDB(JDO) program.
PersistenceManager#detachCopy returns a transient object despite of #PersistenceCapable:detachable is true.
here is a sample code.
I hava a simple test model(POJO)
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")
public class TestModel {
#Persistent(valueStrategy=IdGeneratorStrategy.IDENTITY)
#PrimaryKey
private Long id;
#Persistent
private String name;
// getter, setter
}
detachable is set to "true".
and dao is
public class TestModelDaoImpl {
private PersistenceManagerFactory persistenceManagerFactory;
public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) {
this.persistenceManagerFactory = pmf;
}
public TestModel makePersistent(TestModel obj){
PersistenceManager pm = persistenceManagerFactory.getPersistenceManager();
Transaction tx = pm.currentTransaction();
tx.begin();
pm.makePersistent(obj);
System.out.println(" obj => " + JDOHelper.getObjectState(obj)); // => (1) persistent-new
TestModel detachedObj = pm.detachCopy(obj);
System.out.println(" detachedObj => " + JDOHelper.getObjectState(detachedObj)); // => (2) transient ..
tx.commit();
return detachedObj;
// try catch is omitted
}
}
I think I hava a detached state at (2). but is transient.
Version of ObjectDB is 2.4.0_05
application-context.xml
<bean id="pmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean">
<property name="jdoProperties">
<props>
<prop key="javax.jdo.PersistenceManagerFactoryClass">com.objectdb.jdo.PMF</prop>
<prop key="javax.jdo.option.ConnectionURL">$objectdb/db/testdb.odb</prop>
<prop key="javax.jdo.option.ConnectionUserName">admin</prop>
<prop key="javax.jdo.option.ConnectionPassword">admin</prop>
</props>
</property>
</bean>
<bean id="jdoTransactionManager" class="org.springframework.orm.jdo.JdoTransactionManager">
<property name="persistenceManagerFactory">
<ref local="pmfProxy"/>
</property>
</bean>
<bean id="pmfProxy" class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy">
<property name="targetPersistenceManagerFactory" ref="pmf"/>
<property name="allowCreate" value="true"/>
</bean>
JDO requires enhancement of all the persistable classes. ObjectDB supports using persistable classes with no enhancement, as an extension to JDO, but not all the JDO features can be supported in that mode.
Particularly, when using instances of non enhanced persistence capable classes, transient and detached objects look the same (since the class is missing the extra fields that are added during enhancement to keep additional information).
Running your test with the TestModel class enhanced provides the expected result:
obj => persistent-new
detachedObj => detached-clean
For an answer scroll down to the end of this...
The basic problem is the same as asked multiple time. I have a simple program with two POJOs Event and User - where a user can have multiple events.
#Entity
#Table
public class Event {
private Long id;
private String name;
private User user;
#Column
#Id
#GeneratedValue
public Long getId() {return id;}
public void setId(Long id) { this.id = id; }
#Column
public String getName() {return name;}
public void setName(String name) {this.name = name;}
#ManyToOne
#JoinColumn(name="user_id")
public User getUser() {return user;}
public void setUser(User user) {this.user = user;}
}
The User:
#Entity
#Table
public class User {
private Long id;
private String name;
private List<Event> events;
#Column
#Id
#GeneratedValue
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
#Column
public String getName() { return name; }
public void setName(String name) { this.name = name; }
#OneToMany(mappedBy="user", fetch=FetchType.LAZY)
public List<Event> getEvents() { return events; }
public void setEvents(List<Event> events) { this.events = events; }
}
Note: This is a sample project. I really want to use Lazy fetching here.
Now we need to configure spring and hibernate and have a simple basic-db.xml for loading:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" scope="thread">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://192.168.1.34:3306/hibernateTest" />
<property name="username" value="root" />
<property name="password" value="" />
<aop:scoped-proxy/>
</bean>
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope" />
</entry>
</map>
</property>
</bean>
<bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" scope="thread">
<property name="dataSource" ref="myDataSource" />
<property name="annotatedClasses">
<list>
<value>data.model.User</value>
<value>data.model.Event</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
</props>
</property>
<aop:scoped-proxy/>
</bean>
<bean id="myUserDAO" class="data.dao.impl.UserDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="myEventDAO" class="data.dao.impl.EventDaoImpl">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
</beans>
Note: I played around with the CustomScopeConfigurer and SimpleThreadScope, but that didnt change anything.
I have a simple dao-impl (only pasting the userDao - the EventDao is pretty much the same - except with out the "listWith" function:
public class UserDaoImpl implements UserDao{
private HibernateTemplate hibernateTemplate;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
#SuppressWarnings("unchecked")
#Override
public List listUser() {
return hibernateTemplate.find("from User");
}
#Override
public void saveUser(User user) {
hibernateTemplate.saveOrUpdate(user);
}
#Override
public List listUserWithEvent() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
return users;
}
}
I am getting the org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed at the line with user.getEvents().size();
And last but not least here is the Test class I use:
public class HibernateTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
System.out.println("New user...");
User user = new User();
user.setName("test");
Event event1 = new Event();
event1.setName("Birthday1");
event1.setUser(user);
Event event2 = new Event();
event2.setName("Birthday2");
event2.setUser(user);
udao.saveUser(user);
edao.saveEvent(event1);
edao.saveEvent(event2);
List users = udao.listUserWithEvent();
System.out.println("Events for users");
for (User u : users) {
System.out.println(u.getId() + ":" + u.getName() + " --");
for (Event e : u.getEvents())
{
System.out.println("\t" + e.getId() + ":" + e.getName());
}
}
((ConfigurableApplicationContext)ac).close();
}
}
and here is the Exception:
1621 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
at HibernateTest.main(HibernateTest.java:44)
Exception in thread "main" org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: data.model.User.events, no session or session was closed
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:380)
at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:372)
at org.hibernate.collection.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:119)
at org.hibernate.collection.PersistentBag.size(PersistentBag.java:248)
at data.dao.impl.UserDaoImpl.listUserWithEvent(UserDaoImpl.java:38)
at HibernateTest.main(HibernateTest.java:44)
Things tried but did not work:
assign a threadScope and using beanfactory (I used "request" or "thread" - no difference noticed):
// scope stuff
Scope threadScope = new SimpleThreadScope();
ConfigurableListableBeanFactory beanFactory = ac.getBeanFactory();
beanFactory.registerScope("request", threadScope);
ac.refresh();
...
Setting up a transaction by getting the session object from the deo:
...
Transaction tx = ((UserDaoImpl)udao).getSession().beginTransaction();
tx.begin();
users = udao.listUserWithEvent();
...
getting a transaction within the listUserWithEvent()
public List listUserWithEvent() {
SessionFactory sf = hibernateTemplate.getSessionFactory();
Session s = sf.openSession();
Transaction tx = s.beginTransaction();
tx.begin();
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
tx.commit();
return users;
}
I am really out of ideas by now. Also, using the listUser or listEvent just work fine.
Step forward:
Thanks to Thierry I got one step further (I think). I created the MyTransaction class and do my whole work in there, getting everything from spring. The new main looks like this:
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("basic-db.xml");
// getting dao
UserDao udao = (UserDao) ac.getBean("myUserDAO");
EventDao edao = (EventDao) ac.getBean("myEventDAO");
// gettting transaction template
TransactionTemplate transactionTemplate = (TransactionTemplate) ac.getBean("transactionTemplate");
MyTransaction mt = new MyTransaction(udao, edao);
transactionTemplate.execute(mt);
((ConfigurableApplicationContext)ac).close();
}
Unfortunately now there is a null-pointer Exception #: user.getEvents().size(); (in the daoImpl).
I know that it should not be null (neither from the output in the console nor from the db layout).
Here is the console output for more information (I did a check for user.getEvent() == null and printed "EVENT is NULL"):
New user...
Hibernate: insert into User (name) values (?)
Hibernate: insert into User (name) values (?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
Hibernate: insert into Event (name, user_id) values (?, ?)
List users:
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
1:User1
2:User2
List events:
Hibernate: select event0_.id as id1_, event0_.name as name1_, event0_.user_id as user3_1_ from Event event0_
1:Birthday1 for 1:User1
2:Birthday2 for 1:User1
3:Wedding for 2:User2
Hibernate: select user0_.id as id0_, user0_.name as name0_ from User user0_
Events for users
1:User1 --
EVENT is NULL
2:User2 --
EVENT is NULL
You can get the sample project from http://www.gargan.org/code/hibernate-test1.tgz (it's an eclipse/maven project)
The solution (for console applications)
There are actually two solutions for this problem - depending on your environment:
For a console application you need a transaction template which captures the actutal db logic and takes care of the transaction:
public class UserGetTransaction implements TransactionCallback{
public List users;
protected ApplicationContext context;
public UserGetTransaction (ApplicationContext context) {
this.context = context;
}
#Override
public Boolean doInTransaction(TransactionStatus arg0) {
UserDao udao = (UserDao) ac.getBean("myUserDAO");
users = udao.listUserWithEvent();
return null;
}
}
You can use this by calling:
TransactionTemplate transactionTemplate = (TransactionTemplate) context.getBean("transactionTemplate");
UserGetTransaction mt = new UserGetTransaction(context);
transactionTemplate.execute(mt);
In order for this to work you need to define the template class for spring (ie. in your basic-db.xml):
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transactionManager"/>
</bean>
Another (possible) solution
thanks andi
PlatformTransactionManager transactionManager = (PlatformTransactionManager) applicationContext.getBean("transactionManager");
DefaultTransactionAttribute transactionAttribute = new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
transactionAttribute.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
TransactionStatus status = transactionManager.getTransaction(transactionAttribute);
boolean success = false;
try {
new UserDataAccessCode().execute();
success = true;
} finally {
if (success) {
transactionManager.commit(status);
} else {
transactionManager.rollback(status);
}
}
The solution (for servlets)
Servlets are not that big of a problem. When you have a servlet you can simply start and bind a transaction at the beginning of your function and unbind it again at the end:
public void doGet(...) {
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
// Your code....
TransactionSynchronizationManager.unbindResource(sessionFactory);
}
I think you should not use the hibernate session transactional methods, but let spring do that.
Add this to your spring conf:
<bean id="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory" />
</bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"/>
</bean>
and then I would modify your test method to use the spring transaction template:
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// do your hibernate stuff in here : call save, list method, etc
}
}
}
as a side note, #OneToMany associations are lazy by default, so you don't need to annotate it lazy. (#*ToMany are LAZY by default, #*ToOne are EAGER by default)
EDIT: here is now what is happening from hibernate point of view:
open session (with transaction start)
save a user and keep it in the session (see the session cache as an entity hashmap where the key is the entity id)
save an event and keep it in the session
save another event and keep it in the session
... same with all the save operations ...
then load all users (the "from Users" query)
at that point hibernate see that it has already the object in its session, so discard the one it got from the request and return the one from the session.
your user in the session does not have its event collection initialized, so you get null.
...
Here are some points to enhance your code:
in your model, when collection ordering is not needed, use Set, not List for your collections (private Set events, not private List events)
in your model, type your collections, otherwise hibernate won't which entity to fetch (private Set<Event> events)
when you set one side of a bidirectional relation, and you wish to use the mappedBy side of the relation in the same transaction, set both sides. Hibernate will not do it for you before the next tx (when the session is a fresh view from the db state).
So to address the point above, either do the save in one transaction, and the loading in another one :
public static void main(String[] args) {
// init here (getting dao and transaction template)
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// save here
}
}
transactionTemplate.execute(new TransactionCallback() {
#Override
public Object doInTransaction(TransactionStatus status) {
// list here
}
}
}
or set both sides:
...
event1.setUser(user);
...
event2.setUser(user);
...
user.setEvents(Arrays.asList(event1,event2));
...
(Also do not forget to address the code enhancement points above, Set not List, collection typing)
In case of Web application, it is also possible to declare a special Filter in web.xml, that will do session-per-request:
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
After that you can lazyload your data anytime during the request.
I got here looking for a hint regarding a similar problem. I tried the solution mentioned by Thierry and it didnt work. After that I tried these lines and it worked:
SessionFactory sessionFactory = (SessionFactory) context.getBean("sessionFactory");
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
Indeed what I'm doing is a batch process that must leverage Spring existings managers/services. After loading the context and doing some invocations I founded the famous issue "failed to lazily initialize a collection". Those 3 lines solved it for me.
The issue is that your dao is using one hibernate session but the lazy load of the user.getName (I assume that is where it throws) is happening outside that session -- either not in a session at all or in another session. Typically we open up a hibernate session before we make DAO calls and don't close it until we are done with all lazy loads. Web requests are usually wrapped in a big session so these problems do not happen.
Typically we have wrapped our dao and lazy calls in a SessionWrapper. Something like the following:
public class SessionWrapper {
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.hibernateTemplate = new HibernateTemplate(sessionFactory);
}
public <T> T runLogic(Callable<T> logic) throws Exception {
Session session = null;
// if the session factory is already registered, don't do it again
if (TransactionSynchronizationManager.getResource(sessionFactory) == null) {
session = SessionFactoryUtils.getSession(sessionFactory, true);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
try {
return logic.call();
} finally {
// if we didn't create the session don't unregister/release it
if (session != null) {
TransactionSynchronizationManager.unbindResource(sessionFactory);
SessionFactoryUtils.releaseSession(session, sessionFactory);
}
}
}
}
Obviously the SessionFactory the same SessionFactory that was injected into your dao.
In your case, you should wrap the entire listUserWithEvent body in this logic. Something like:
public List listUserWithEvent() {
return sessionWrapper.runLogic(new Callable<List>() {
public List call() {
List users = hibernateTemplate.find("from User");
for (User user : users) {
System.out.println("LIST : " + user.getName() + ":");
user.getEvents().size();
}
}
});
}
You will need to inject the SessionWrapper instance into your daos.
Interesting!
I had the same problem in a #Controller's #RequestMapping handler method.
The simple solution was to add a #Transactional annotation to the handler method so that the session is kept open for the whole duration of the method body execution
Easiest solution to implement:
Within the scope of the session[inside the API annotated with #Transactional], do the following:
if A had a List<B> which is lazily loaded, simply call an API which makes sure the List is loaded
What's that API ?
size(); API of the List class.
So all that's needed is:
Logger.log(a.getBList.size());
This simple call of logging the size makes sure it gets the whole list before calculating the size of the list. Now you will not get the exception !
What worked for us in JBoss was the solution #2 taken from this site at Java Code Geeks.
Web.xml:
<filter>
<filter-name>ConnectionFilter</filter-name>
<filter-class>web.ConnectionFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ConnectionFilter</filter-name>
<url-pattern>/faces/*</url-pattern>
</filter-mapping>
ConnectionFilter:
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;
public class ConnectionFilter implements Filter {
#Override
public void destroy() { }
#Resource
private UserTransaction utx;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
utx.begin();
chain.doFilter(request, response);
utx.commit();
} catch (Exception e) { }
}
#Override
public void init(FilterConfig arg0) throws ServletException { }
}
Maybe it would work with Spring too.