I have recently started working on a project with SOAP webservices, Spring and Hibernate.
I am facing the following issue:
We use SOAP UI to send requests to test our code. I have written a service which processes bills. Basically there are 2 services, one creates a bill and the other processes that bill.
We have a table called BillTb. Before processing a bill, we check the status of the bill. If the bill status is 3(pending), we process it. If it is not equal to 3, we do not process it. Once the bill is processed, we change the status to 4(processed).
Now if the bill status is 3, we do a number of entries in other tables and at last, status is changed to 4.
If in between processing, if the processing fails, we need to revert all those entries. So we call these entries within a transaction.
The DAO layer with hibernate code is as follows:
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.persistence.Query;
#PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;
public class BillDAOImpl implements BillDao {
...
...
...
int pendingStatus = 3;
int processedStatus = 4;
Session session = null;
for(int id: ids){
Bill bill = null;
try{
session = entityManager.unwrap(Session.class);
bill= entityManager.find(Bill.class, id);
session.getTransaction().begin();
if(bill.status() != pendingStatus ){
System.out.println("The bill is already processed");
continue;
}
...
...
bill.setStatus(processedStatus);
entityManager.persist(bill);
session.getTransaction().commit();
} catch(Exception e){
}
}
}
Now the problem is, once a bill status is changed from 3 to 4, if I change the status again to 3 by firing an update query in database, it should again work, but somehow, it reads the status as 4 only.
If I bring down the server, then execute the request again then it works for same entry.
The other transaction related parameters are set as :
<property name="hibernate.cache.use_query_cache" value="false" />
Also,
<bean id="projectEntityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
p:persistenceXmlLocation="classpath*:META-INF/persistence.xml"
p:persistenceUnitName="persistenceUnit" p:loadTimeWeaver-ref="loadTimeWeaver"
p:jpaVendorAdapter-ref="jpaVendorAdapter" p:jpaDialect-ref="jpaDialect"
p:dataSource-ref="datasourceBean">
<property name="jpaProperties">
<props>
<prop key="hibernate.transaction.manager_lookup_class">org.hibernate.transaction.BTMTransactionManagerLookup
</prop>
<prop key="hibernate.transaction.flush_before_completion">false</prop>
...
...
<prop key="hibernate.connection.isolation">3</prop>
<prop key="hibernate.connection.release_mode">auto</prop>
</props>
</property>
</bean>
So here it seems that session is somehow storing the bill object and when I update the bill object directly in database, it stores stale data. So what should be done to in this case. Should I clear the session at end of method?
You should perform the query inside of the transaction and also remember to commit the transaction everytime (if you trigger continue, that is ommited).
Actually i would write it like this:
for(int id: ids){
Bill bill = null;
Transaction tx = session.getTransaction();
tx.begin();
try{
bill= entityManager.find(Bill.class, id);
if(bill.status() != pendingStatus ){
System.out.println("The bill is already processed");
tx.commit();
continue;
}
bill.setStatus(processedStatus);
entityManager.persist(bill);
session.flush();
tx.commit();
}catch(Exception e){
tx.rollback();
throw e;
}
}
Related
Preamble - using Spring
I am confused as to the purpose of the spring #Transactional annotation. I thought from a few blog posts I've read that it would allow me to simplify transaction management and just write this, and it would handle connection/commit/rollback automagically:
public class DaoImpl implements Dao {
#Autowired
private SessionFactory sessionFactory;
#Transactional
public void saveOrUpdateOne(final AdditionalDataItem item) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(p_item);
}
}
However this gives me an exception: " Calling method 'saveOrUpdate' is not valid without an active transaction"
If I instead change the save method to this, it all works - so my question is, what is #Transactional doing?
#Override
#Transactional
public void saveOrUpdateOne(final AdditionalDataItem p_item) {
Session session = null;
Transaction trans = null;
try {
session = sessionFactory.getCurrentSession();
trans = session.beginTransaction();
TransactionStatus status = trans.getStatus();
session.saveOrUpdate(p_item);
trans.commit();
} catch (Exception e) {
LOGGER.error("Exception saving data: {}", e.getMessage());
if (trans != null) {
try {
trans.rollback();
} catch (RuntimeException rbe) {
LOGGER.error("Couldn’t roll back transaction", rbe);
}
}
} finally {
if (session != null && session.isOpen()) {
try {
session.close();
} catch (HibernateException ne) {
LOGGER.error("Couldn’t close session", ne);
}
}
}
}
For reference, I'm using Java 11 with Spring Framework 5.3.7 and hibernate 5.5.7 and have appropriate dao, session factory and tx manager beans:
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="${sessionFactory.datasource}" />
<property name="configLocation" value="${sessionFactory.configLocation}" />
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="Dao" class="com.xxxxx.dao.DaoImpl">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
It is because you do not enable #Transactional yet and also not begin the transaction when calling session.saveOrUpdate(), so it gives you the error 'not valid without an active transaction' .
To enable #Transactional, you have to use #EnableTransactionManagement or add <tx:annotation-driven/> in case you are using XML configuration . It basically does the following for you (source) :
#EnableTransactionManagement and <tx:annotation-driven/> are
responsible for registering the necessary Spring components that power
annotation-driven transaction management, such as the
TransactionInterceptor and the proxy- or AspectJ-based advice that
weaves the interceptor into the call stack when JdbcFooRepository's
#Transactional methods are invoked.
Your working example works because you manually manage the transaction by yourself .It is nothing to do with #Transactional since you never enable it.
Take the working codes as an example , what #Transactional does for you is that you no longer need to manually write the following transaction codes as all of them will be encapsulated in the TransactionInterceptor and execute around your #Transactional method based on AOP :
public Object invoke() {
Session session = null;
Transaction trans = null;
try {
session = sessionFactory.getCurrentSession();
trans = session.beginTransaction();
TransactionStatus status = trans.getStatus();
/***********************************************/
Here it will invoke your #Transactional Method
/************************************************/
trans.commit();
} catch (Exception e) {
LOGGER.error("Exception saving data: {}", e.getMessage());
if (trans != null) {
try {
trans.rollback();
} catch (RuntimeException rbe) {
LOGGER.error("Couldn’t roll back transaction", rbe);
}
}
} finally {
if (session != null && session.isOpen()) {
try {
session.close();
} catch (HibernateException ne) {
LOGGER.error("Couldn’t close session", ne);
}
}
}
}
So you can see that your #Transactional method will become very clean after removing these "ceremony" codes.
#Transactional is used if you want to update 2 tables if one of them failed the other one will rollback automatic
you can use it above method
and you can call save or update using bean of a Repository class
I'm working in Spring project, using mybatis 3 and oracle 11g.
I tried to rollback transactions when errors happen. However, rollback seems not to be working.
Source code bellow:
ApplicationContext.xml
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
Service
int changeLimitSrcAcc(String src_acctno,String dest_acctno, String amt);
ServiceImpl
#Override
public int changeLimitSrcAcc(String src_acctno, String dest_acctno,
String amt) {
int result = 0;
SqlSessionFactory sqlMapper = MyBatisService.getSessionFactory();
SqlSession sqlSession = sqlMapper.openSession();
CustomerAccountMapper mapper = sqlSession
.getMapper(CustomerAccountMapper.class);
try {
int result1 = mapper.changeLimitSrcAcc(src_acctno, amt);
int result2 = mapper.changeLimitDescAcc(dest_acctno, amt);
if (result1 != 1 || result2 != 1)
throw new Exception("Error happened");
else result = 1;
sqlSession.commit();
} catch (Exception e) {
System.out.println(e.getMessage());
sqlSession.rollback();
} finally {
sqlSession.close();
}
return result;
}
I also tried to rollback a single transaction but it still committed.
I read on Mybatis home page and it said #transaction annotations doesn't need. I also put the annotations and nothing happened.
Any solutions?
Thanks.
You have 2 ways of managing transactions:
Declarative Transaction Management (#Transactional annotation)
Programmatic Transaction Management (manually do commit/rollback)
You try to do both. Decide - either let Spring manage transactions - configure myBatis with Spring config - more simple way or manually create DefaultTransactionDefinition etc.
I'm trying to use Hibernate Search in my project (writing tests right now using junit + dbunit), but searching query doesn't return any results. I worked on this yesterday and got to conclusion that problem is Hibernate Search doesn't work well with dbunit #DatabaseSetup (similiar problem as in this unanswered question: link). I will go with more details, but firs things first, there is my entity class:
#Entity
#Indexed
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "userId")
private Long id;
(...)
#Column(nullable = false, unique = true)
#Field(index = Index.YES, analyze=Analyze.YES, store=Store.NO)
private String email;
(...)
#Column(nullable = false, unique = true)
#Field(index = Index.YES, analyze=Analyze.YES, store=Store.NO)
private String username;
(...)
}
I save it to db by my DAO:
#Repository
public class UserDAOImpl implements UserDAO {
#Autowired
private SessionFactory sessionFactory;
#Override
public long save(User toSave) {
return (Long) this.sessionFactory.getCurrentSession().save(toSave);
}
(...)
}
This is code responsible for running lucene query:
#Override
public List<User> searchByEmail(String email) throws InterruptedException {
return generateHibernateSearchQueryFor("email", email).list();
}
private org.hibernate.Query generateHibernateSearchQueryFor(String field, String searchParam) {
FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
QueryBuilder queryBuilder = fullTextSession.getSearchFactory().buildQueryBuilder().forEntity(User.class).get();
org.apache.lucene.search.Query lQuery = queryBuilder.keyword().onFields(field).matching(searchParam).createQuery();
org.hibernate.Query fullTextQuery = fullTextSession.createFullTextQuery(lQuery, User.class);
return fullTextQuery;
}
And this is how thing is configured in spring config:
<bean id="hibernate4AnnotatedSessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="packagesToScan" value="me.ksiazka.model" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</prop>
<prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.hbm2ddl.auto">create</prop>
<prop key="hibernate.search.default.directory_provider">filesystem</prop>
<prop key="hibernate.search.default.indexBase">src/searching_indexes</prop>
</props>
</property>
</bean>
Now how did I test at first. I configured my testing dataset with dbunit and created testing method like this:
#Test
#DatabaseSetup("classpath:/testsDataset.xml")
public void searchByEmailTest() {
User u1 = new User("Maciej", "Adamowicz", "k2", "mac#gmial.com", "MacAda");
userDAO.save(u1);
List<User> u = null;
try {
//It worked at first - as new user was saved with hibernate he got his index in hibernate search indexes folder and searching found him.
u = searchService.searchByEmail("mac#gmail.com");
} catch (InterruptedException e) {
e.printStackTrace();
}
//I know there should be asserts, its just for simplification for need of moment.
System.out.println(":: " + u.size());
System.out.println(":: " + u.get(0).getName());
}
List<User> u2 = null;
try {
//abc#gmial.com is in db - setted up by #DatabaseSetup
u2 = searchService.searchByEmail("abc#gmail.com");
} catch (InterruptedException e) {
e.printStackTrace();
}
//This didnt work, rows putted into db by dbunit doesn't have indexes in my indexing folder.
System.out.println(":: " + u2.size());
System.out.println(":: " + u2.get(0).getName());
}
After looking Hibernate Search documentation i found fullTextSession.createIndexer().startAndWait();
method. I used it but it still doesn't work for rows from #DatabaseSetup. Anyway it worked with rows that I putted before test "by hand" with sql so I thought it is only problem with dbunit and just wrote setup with #Before:
#Before
public void setupDatabase() {
if(!doneBefore) {
try {
//It calls createIndexer().startAndWait() to make sure everything is indexed before test
searchService.reindex();
} catch (InterruptedException e) {
e.printStackTrace();
}
User u1 = new User("Maciej", "Adamowicz", "k2", "mac#gmial.com", "MacAda");
userDAO.save(u1);
doneBefore = true;
}
}
And run this test:
#Test
public void searchByEmailTest() {
List<User> u = null;
try {
u = searchService.searchByEmail("mac#gmail.com");
} catch (InterruptedException e) {
e.printStackTrace();
}
//Also asserts here, I know.
System.out.println(":: " + u.size());
System.out.println(":: " + u.get(0).getName());
}
And it doesn't work althought data is saved by hibernate. I tried to find bug and reverted my code to eariel version where test was passing (the one with #DatabaseSetup but only for rows saved with my dao) and now this one doesn't pass too. I'm quite confused and out of ideas why it does not index new objects, not to say why it does not reindex all database when massive indexer is called. Any help will be appreciated.
EDIT:
After potential answers I did some more tests. In regards to fact that searching sometimes resulted with doubled or tripled rows I tried .purgeAll() and changing indexing provider to RAM to be sure that my indexes are clean when starting testing. It didn't change basicly anyting. To build my index I used .startAndWait() as mentioned before. Tried with building it "by hand" with .index() but I got some nested transactions problems when tried to use fullTextSession. Explicitly commiting transaction (or setting #Rollback(false) - tried both) doesn't work too.
Everything I tried I found at Hibernate Search documentation - link.
Indexing and searching work fine if I save something with DAO just before searching for it, but doing the same it #Before and then searching just doesn't work.
When I remember right, then Hibernate Search will update the index when you submit a transaction.
This is no problem for normal code, but in tests this behaviour can cause a problem, because a common pattern for tests is, that you start a transaction when you start the test, and at the end of the test you role the transaction back, but you never submit them.
To verify that this is the cause for your problem, create a test that start an explicite new transaction, modifiy something and then commit the transaction. Then after the commit check your hiberante search index.
As mentioned in this Hibernate Search doesn't index/reindex entities, you need to explicitly commit your transaction after saving data for indexing to occur. Indexing occurs on a post transaction synchronization (at least per default).
You can try to use the manual indexing API or the mass indexer. I am not sure why this did not work for you. I am also not sure how exactly #DatabaseSetup works and hooks into the JUnit life-cycle.
Regarding the triple results. You might be using a file system based index (used per default) which creates a file based Lucene index which is not cleaned up between test runs. Use a RAM index or make sure the file based index gets cleaned up.
It might help, if you share your Hibernate properties configuration.
I have a standalone Thread application. Which is a listener waiting for a message, and when it arrives do somethings, in which I have to save in the DB the message.
But I have problems because if I run the application and "send manually a message" everythings works fine, but if I run the application and wait for a message of the system (for instance one hour after it arrives) when the APP 's going to save to the DB it sinks, telling:
java.sql.SQLException: Io exception: Connection reset
or
java.sql.BatchUpdateException: Io Exception: Connection reset
I'm using Hibernate 3.2.6 with C3p0 0.9.2.1
The configuration of the session is:
public final class PersistenceUtil{
private static final SessionFactory sessionFactory;
static {
try {
String location = ServiceLocator.getInstance().getConfigurationService().getProperty("adaptor.location");
if (LOC_1.equals(location)) {
sessionFactory = new AnnotationConfiguration().configure("hibernate.1.cfg.xml").buildSessionFactory();
} else if(LOC_2.equals(location)) {
sessionFactory = new AnnotationConfiguration().configure("hibernate.2.cfg.xml").buildSessionFactory();
}else {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
}
sessionFactory.openSession();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
private PersistenceUtil() {
}
public static void shutdown() {
try {
sessionFactory.close();
} catch (HibernateException e) {
LOG.error("PersistanceUtil.shutdown Error: " + e.getMessage());
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
When I want to save I do (and where is the error):
public T save(T entity) {
if (!getSession().getTransaction().isActive()) {
log.warn("Session not active. Starting the session");
getSession().beginTransaction();
}
getSession().save(entity);
getSession().getTransaction().commit();
return entity;
}
And my hibernate.cfg.xml is:
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<property name="connection.driver_class">oracle.jdbc.driver.OracleDriver</property>
<property name="connection.url">URL</property>
<property name="connection.username">USER</property>
<property name="connection.password">Password</property>
<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.Oracle10gDialect</property>
<!-- <property name="hibernate.hbm2ddl.auto">update</property> -->
<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>
<property name="hibernate.transaction.factory_class">org.hibernate.transaction.JDBCTransactionFactory</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<property name="hibernate.c3p0.min_size">1</property>
<property name="hibernate.c3p0.max_size">100</property>
<property name="hibernate.c3p0.timeout">3000</property>
<property name="hibernate.c3p0.max_statements">0</property>
<property name="hibernate.c3p0.idle_test_period">0</property>
<mapping class="MessageEO" />
<mapping class="CustomerEO" />
</session-factory>
</hibernate-configuration>
What am I doing wrong?
Thanks in advance
Why don't you create new session before attempting to save your data? According to Hibernate's docs this is proper approach for atomic operation. As I can see your comments in code, you think that starting transaction means starting new session or opening new connection whitch is not true. You can have multiple (but not always nested) transactions per session.
I always use following template for atomic operation - it never let me down. I even have this piece of code as template in Eclipse:
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
// save/ update / delete your entities here
tx.commit();
} catch (RuntimeException ex) {
if (tx != null) {
tx.rollback();
}
throw ex;
} finally {
session.close();
}
Principles:
Hold single SessionFactory object. Creating it is expensive.
For every bulk of operation (saving, modifing etc.) or single entities open new session using factory - it is lightweight and thread safe.
Sessions itself are not thread safe.
Always start new transactions and roll commit/rollback them if needed. Even for read-only data fetches.
Always close your session after your bulk operations are done (releasing connections etc.)
Never use same same session in whitch exception has had occured.
I'm adding envers to an existing hibernate entities. Everything is working smoothly so far as far as auditing, however querying is a different issue because the revision tables aren’t populated with the existing data. Has anyone else already solved this issue? Maybe you’ve found some way to populate the revision tables with the existing table? Just thought I’d ask, I'm sure others would find it useful.
We populated the initial data by running a series of raw SQL queries to simulate "inserting" all the existing entities as if they had just been created at the same time. For example:
insert into REVINFO(REV,REVTSTMP) values (1,1322687394907);
-- this is the initial revision, with an arbitrary timestamp
insert into item_AUD(REV,REVTYPE,id,col1,col1) select 1,0,id,col1,col2 from item;
-- this copies the relevant row data from the entity table to the audit table
Note that the REVTYPE value is 0 to indicate an insert (as opposed to a modification).
You'll have a problem in this category if you are using Envers ValidityAuditStrategy and have data which has been created other than with Envers enabled.
In our case (Hibernate 4.2.8.Final) a basic object update throws "Cannot update previous revision for entity and " (logged as [org.hibernate.AssertionFailure] HHH000099).
Took me a while to find this discussion/explanation so cross-posting:
ValidityAuditStrategy with no audit record
You don't need to.
AuditQuery allows you to get both RevisionEntity and data revision by :
AuditQuery query = getAuditReader().createQuery()
.forRevisionsOfEntity(YourAuditedEntity.class, false, false);
This will construct a query which returns a list of Object [3]. Fisrt element is your data, the second is the revision entity and the third is the type of revision.
We have solved the issue of populating the audit logs with the existing data as follows:
SessionFactory defaultSessionFactory;
// special configured sessionfactory with envers audit listener + an interceptor
// which flags all properties as dirty, even if they are not.
SessionFactory replicationSessionFactory;
// Entities must be retrieved with a different session factory, otherwise the
// auditing tables are not updated. ( this might be because I did something
// wrong, I don't know, but I know it works if you do it as described above. Feel
// free to improve )
FooDao fooDao = new FooDao();
fooDao.setSessionFactory( defaultSessionFactory );
List<Foo> all = fooDao.findAll();
// cleanup and close connection for fooDao here.
..
// Obtain a session from the replicationSessionFactory here eg.
Session session = replicationSessionFactory.getCurrentSession();
// replicate all data, overwrite data if en entry for that id already exists
// the trick is to let both session factories point to the SAME database.
// By updating the data in the existing db, the audit listener gets triggered,
// and inserts your "initial" data in the audit tables.
for( Foo foo: all ) {
session.replicate( foo, ReplicationMode.OVERWRITE );
}
The configuration of my data sources (via Spring):
<bean id="replicationDataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="org.postgresql.Driver"/>
<property name="url" value=".."/>
<property name="username" value=".."/>
<property name="password" value=".."/>
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
<bean id="auditEventListener"
class="org.hibernate.envers.event.AuditEventListener"/>
<bean id="replicationSessionFactory"
class="o.s.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="entityInterceptor">
<bean class="com.foo.DirtyCheckByPassInterceptor"/>
</property>
<property name="dataSource" ref="replicationDataSource"/>
<property name="packagesToScan">
<list>
<value>com.foo.**</value>
</list>
</property>
<property name="hibernateProperties">
<props>
..
<prop key="org.hibernate.envers.audit_table_prefix">AUDIT_</prop>
<prop key="org.hibernate.envers.audit_table_suffix"></prop>
</props>
</property>
<property name="eventListeners">
<map>
<entry key="post-insert" value-ref="auditEventListener"/>
<entry key="post-update" value-ref="auditEventListener"/>
<entry key="post-delete" value-ref="auditEventListener"/>
<entry key="pre-collection-update" value-ref="auditEventListener"/>
<entry key="pre-collection-remove" value-ref="auditEventListener"/>
<entry key="post-collection-recreate" value-ref="auditEventListener"/>
</map>
</property>
</bean>
The interceptor:
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
..
public class DirtyCheckByPassInterceptor extends EmptyInterceptor {
public DirtyCheckByPassInterceptor() {
super();
}
/**
* Flags ALL properties as dirty, even if nothing has changed.
*/
#Override
public int[] findDirty( Object entity,
Serializable id,
Object[] currentState,
Object[] previousState,
String[] propertyNames,
Type[] types ) {
int[] result = new int[ propertyNames.length ];
for ( int i = 0; i < propertyNames.length; i++ ) {
result[ i ] = i;
}
return result;
}
}
ps: keep in mind that this is a simplified example. It will not work out of the box but it will guide you towards a working solution.
Take a look at http://www.jboss.org/files/envers/docs/index.html#revisionlog
Basically you can define your own 'revision type' using #RevisionEntity annotation,
and then implement a RevisionListener interface to insert your additional audit data,
like current user and high level operation. Usually those are pulled from ThreadLocal context.
You could extend the AuditReaderImpl with a fallback option for the find method, like:
public class AuditReaderWithFallback extends AuditReaderImpl {
public AuditReaderWithFallback(
EnversService enversService,
Session session,
SessionImplementor sessionImplementor) {
super(enversService, session, sessionImplementor);
}
#Override
#SuppressWarnings({"unchecked"})
public <T> T find(
Class<T> cls,
String entityName,
Object primaryKey,
Number revision,
boolean includeDeletions) throws IllegalArgumentException, NotAuditedException, IllegalStateException {
T result = super.find(cls, entityName, primaryKey, revision, includeDeletions);
if (result == null)
result = (T) super.getSession().get(entityName, (Serializable) primaryKey);
return result;
}
}
You could add a few more checks in terms of returning null in some cases.
You might want to use your own factory as well:
public class AuditReaderFactoryWithFallback {
/**
* Create an audit reader associated with an open session.
*
* #param session An open session.
* #return An audit reader associated with the given sesison. It shouldn't be used
* after the session is closed.
* #throws AuditException When the given required listeners aren't installed.
*/
public static AuditReader get(Session session) throws AuditException {
SessionImplementor sessionImpl;
if (!(session instanceof SessionImplementor)) {
sessionImpl = (SessionImplementor) session.getSessionFactory().getCurrentSession();
} else {
sessionImpl = (SessionImplementor) session;
}
final ServiceRegistry serviceRegistry = sessionImpl.getFactory().getServiceRegistry();
final EnversService enversService = serviceRegistry.getService(EnversService.class);
return new AuditReaderWithFallback(enversService, session, sessionImpl);
}
}
I've checked many ways, but the best way for me is to write a PL/SQL script as below.
The below script is written for PostgreSQL. Didn't check other vendors, but they must have the same feature.
CREATE SEQUENCE hibernate_sequence START 1;
DO
$$
DECLARE
u RECORD;
next_id BIGINT;
BEGIN
FOR u IN SELECT * FROM user
LOOP
SELECT NEXTVAL('hibernate_sequence')
INTO next_id;
INSERT INTO revision (rev, user_id, timestamp)
VALUES (next_id,
'00000000-0000-0000-0000-000000000000',
(SELECT EXTRACT(EPOCH FROM NOW() AT TIME ZONE 'utc')) * 1000);
INSERT INTO user_aud(rev,
revend,
revtype,
id,
created_at,
created_by,
last_modified_at,
last_modified_by,
name)
VALUES (next_id,
NULL,
0,
f.id,
f.created_at,
f.created_by,
f.last_modified_at,
f.last_modified_by,
f.name);
END LOOP;
END;
$$;