Populate envers revision tables with existing data from Hibernate Entities - java

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

Related

Multiple Databases in Spring Hibernate

I want to ask how i connect second database in my program . First i tell you this is a inventry system . I want to get functionality that when i update the record in existing data base record also update into other databases. please checkout the code . I use this code for single database connection and for the update record .
I create another button in my webpage . i need that when i click the second button record save in second database .
jdbc.driver=com.mysql.jdbc.jdbc2.optional.MysqlDataSource
jdbc.driver2=com.mysql.jdbc.Driver
jdbc.dialect=org.hibernate.dialect.MySQLDialect
jdbc.databaseurl=jdbc:mysql://localhost:3306/bvasdb?zeroDateTimeBehavior=convertToNull&autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
jdbc.username=admin
jdbc.password=admin
<beans:property name="dataSource">
<beans:bean class="${jdbc.driver}">
<beans:property name="url" value="${jdbc.databaseurl}" />
<beans:property name="user" value="${jdbc.username}" />
<beans:property name="password" value="${jdbc.password}" />
</beans:bean>
#RequestMapping("partmaintenanceupdate")
public ModelAndView partmaintenanceUpdate(#RequestParam("partmaintenancemode") String partmaintenancemode,
#RequestParam("searchpartno") String searchpartno,
#RequestParam("makemodelcodeselected") String makemodelcodeselected,
#RequestParam("subcategoryselected") String subcategoryselected,
#RequestParam("ordertypeselected") String ordertypeselected, Model map, HttpSession session,
ModelAndView mav) {
AppUser user = (AppUser) session.getAttribute("user");
if (user == null) {
throw new OrderNotFoundException();
} else {
LOGGER.info("#partmaintenanceupdate");
if (subcategorylistdd.size() == 0) {
subcategorylistdd = mainService.getAllSubCategory();
}
if (makemodellistdd.size() == 0) {
makemodellistdd = mainService.getAllMakeModelMap();
}
if (ordertypelistdd.size() == 0) {
ordertypelistdd = mainService.getAllOrderType();
}
partmaintenancemode = "update";
parts.setPartno(request.getParameter("partno").trim().toUpperCase());
parts.setInterchangeno(request.getParameter("interchangeno").trim().toUpperCase());
parts.setYear(request.getParameter("year").trim());
parts.setYearfrom(Integer.parseInt(request.getParameter("yearfrom").trim()));
parts.setYearto(Integer.parseInt(request.getParameter("yearto").trim()));
parts.setPartdescription(request.getParameter("partdescription").trim());
parts.setActualprice(new BigDecimal(request.getParameter("actualprice").trim()));
parts.setCostprice(new BigDecimal(request.getParameter("costprice").trim()));
parts.setListprice(new BigDecimal(request.getParameter("listprice").trim()));
parts.setWholesaleprice(new BigDecimal(request.getParameter("wholesaleprice").trim()));
parts.setUnitsinstock(Integer.parseInt(request.getParameter("unitsinstock").trim()));
parts.setUnitsonorder(Integer.parseInt(request.getParameter("unitsonorder").trim()));
parts.setReorderlevel(Integer.parseInt(request.getParameter("reorderlevel").trim()));
parts.setSafetyquantity(Integer.parseInt(request.getParameter("safetyquantity").trim()));
parts.setKeystonenumber(request.getParameter("keystonenumber").trim());
parts.setOemnumber(request.getParameter("oemnumber").trim());
parts.setDpinumber(request.getParameter("dpinumber").trim());
parts.setLocation(request.getParameter("location").trim());
parts.setCapa(request.getParameter("capa").trim());
parts.setMakemodelcode(makemodellistdd.get(request.getParameter("makemodelcodeselected").trim()));
parts.setSubcategory(subcategorylistdd.get(request.getParameter("subcategoryselected").trim()));
parts.setOrdertype(request.getParameter("ordertypeselected").trim());
partsService.updatePartsMaintenance(parts);
mav.clear();
mav.setViewName("partmaintenancepage");
mav.addObject("user", user);
mav.addObject("branch", branch);
mav.addObject("appcss", appcss);
mav.addObject("sysdate", InsightUtils.getNewUSDate());
mav.addObject("partmaintenancemode", partmaintenancemode);
mav.addObject("subcategorylistdd", subcategorylistdd);
mav.addObject("makemodellistdd", makemodellistdd);
mav.addObject("ordertypelistdd", ordertypelistdd);
mav.addObject("makemodelcodeselected", makemodelcodeselected);
mav.addObject("subcategoryselected", subcategoryselected);
mav.addObject("ordertypeselected", ordertypeselected);
mav.addObject("searchpartno", searchpartno);
mav.addObject("parts", parts);
return mav;
}
}
#Transactional
public void updatePartsMaintenance(Parts parts) {
Session session = sessionFactory.getCurrentSession();
session.saveOrUpdate(parts);
// updateSubcategoryOnParts();
// updateMakemodelOnParts();
session.flush();
session.clear();
}
I create a button in my webpage . i need to update the data in second database only when i click the button .
If you need more then one DB you need more then one HibernateSessionFactory.
You just create an hibernate factory for each DB with the relevant table mapping in each configuration.
Create Multiple Datasource beans and use #Qualifier while autowiring to select your desired connection.

Data persisting in session object of hibernate after the request is processed

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

Hibernate Search doesn't index/reindex entities

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.

JOOQ & transactions

I've been reading about transactions & jooq but I struggle to see how to implement it in practice.
Let's say I provide JOOQ with a custom ConnectionProvider which happens to use a connection pool with autocommit set to false.
The implementation is roughly:
#Override public Connection acquire() throws DataAccessException {
return pool.getConnection();
}
#Override public void release(Connection connection) throws DataAccessException {
connection.commit();
connection.close();
}
How would I go about wrapping two jooq queries into a single transaction?
It is easy with the DefaultConnectionProvider because there's only one connection - but with a pool I'm not sure how to go about it.
jOOQ 3.4 Transaction API
With jOOQ 3.4, a transaction API has been added to abstract over JDBC, Spring, or JTA transaction managers. This API can be used with Java 8 as such:
DSL.using(configuration)
.transaction(ctx -> {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
});
Or with pre-Java 8 syntax
DSL.using(configuration)
.transaction(new TransactionRunnable() {
#Override
public void run(Configuration ctx) {
DSL.using(ctx)
.update(TABLE)
.set(TABLE.COL, newValue)
.where(...)
.execute();
}
});
The idea is that the lambda expression (or anonymous class) form the transactional code, which:
Commits upon normal completion
Rolls back upon exception
The org.jooq.TransactionProvider SPI can be used to override the default behaviour, which implements nestable transactions via JDBC using Savepoints.
A Spring example
The current documentation shows an example when using Spring for transaction handling:
http://www.jooq.org/doc/latest/manual/getting-started/tutorials/jooq-with-spring/
This example essentially boils down to using a Spring TransactionAwareDataSourceProxy
<!-- Using Apache DBCP as a connection pooling library.
Replace this with your preferred DataSource implementation -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
init-method="createDataSource" destroy-method="close">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:~/maven-test" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<!-- Using Spring JDBC for transaction management -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="transactionAwareDataSource"
class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg ref="dataSource" />
</bean>
<!-- Bridging Spring JDBC data sources to jOOQ's ConnectionProvider -->
<bean class="org.jooq.impl.DataSourceConnectionProvider"
name="connectionProvider">
<constructor-arg ref="transactionAwareDataSource" />
</bean>
A running example is available from GitHub here:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-example
A Spring and Guice example
Although I personally wouldn't recommend it, some users have had success replacing a part of Spring's DI by Guice and handle transactions with Guice. There is also an integration-tested running example on GitHub for this use-case:
https://github.com/jOOQ/jOOQ/tree/master/jOOQ-examples/jOOQ-spring-guice-example
This is probably not the best way but it seems to work. The caveat is that it is not the release but the commit method which closes the connection and returns it to the pool, which is quite confusing and could lead to issues if some code "forgets" to commit...
So the client code looks like:
final PostgresConnectionProvider postgres =
new PostgresConnectionProvider("localhost", 5432, params.getDbName(), params.getUser(), params.getPass())
private static DSLContext sql = DSL.using(postgres, SQLDialect.POSTGRES, settings);
//execute some statements here
sql.execute(...);
//and don't forget to commit or the connection will not be returned to the pool
PostgresConnectionProvider p = (PostgresConnectionProvider) sql.configuration().connectionProvider();
p.commit();
And the ConnectionProvider:
public class PostgresConnectionProvider implements ConnectionProvider {
private static final Logger LOG = LoggerFactory.getLogger(PostgresConnectionProvider.class);
private final ThreadLocal<Connection> connections = new ThreadLocal<>();
private final BoneCP pool;
public PostgresConnectionProvider(String serverName, int port, String schema, String user, String password) throws SQLException {
this.pool = new ConnectionPool(getConnectionString(serverName, port, schema), user, password).pool;
}
private String getConnectionString(String serverName, int port, String schema) {
return "jdbc:postgresql://" + serverName + ":" + port + "/" + schema;
}
public void close() {
pool.shutdown();
}
public void commit() {
LOG.debug("Committing transaction in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection != null) {
connection.commit();
connection.close();
connections.set(null);
}
} catch (SQLException ex) {
throw new DataAccessException("Could not commit transaction in postgres pool", ex);
}
}
#Override
public Connection acquire() throws DataAccessException {
LOG.debug("Acquiring connection in {}", Thread.currentThread());
try {
Connection connection = connections.get();
if (connection == null) {
connection = pool.getConnection();
connection.setAutoCommit(false);
connections.set(connection);
}
return connection;
} catch (SQLException ex) {
throw new DataAccessException("Can't acquire connection from postgres pool", ex);
}
}
#Override
//no-op => the connection won't be released until it is commited
public void release(Connection connection) throws DataAccessException {
LOG.debug("Releasing connection in {}", Thread.currentThread());
}
}
Easiest way,(I have found) to use Spring Transactions with jOOQ, is given here: http://blog.liftoffllc.in/2014/06/jooq-and-transactions.html
Basically we implement a ConnectionProvider that uses org.springframework.jdbc.datasource.DataSourceUtils.doGetConnection(ds) method to find and return the DB connection that holds transaction created by Spring.
Create a TransactionManager bean for your DataSource, example shown below:
<bean
id="dataSource"
class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="mysql://locahost:3306/db_name"
p:username="root"
p:password="root"
p:initialSize="2"
p:maxActive="10"
p:maxIdle="5"
p:minIdle="2"
p:testOnBorrow="true"
p:validationQuery="/* ping */ SELECT 1"
/>
<!-- Configure the PlatformTransactionManager bean -->
<bean
id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"
/>
<!-- Scan for the Transactional annotation -->
<tx:annotation-driven/>
Now you can annotate all the classes or methods which uses jOOQ's DSLContext with
#Transactional(rollbackFor = Exception.class)
And while creating the DSLContext object jOOQ will make use of the transaction created by Spring.
Though its an old question, Please look at this link to help configure JOOQ to use spring provided transaction manager. Your datasource and DSLContext have to be aware of Transacation.
https://www.baeldung.com/jooq-with-spring
You may have to change
#Bean
public DefaultDSLContext dsl() {
    return new DefaultDSLContext(configuration());
}
to
#Bean
public DSLContext dsl() {
    return new DefaultDSLContext(configuration());
}

Spring JDBC DAO

Im learning Spring (2 and 3) and i got this method in a ClientDao
public Client getClient(int id) {
List<Client> clients= getSimpleJdbcTemplate().query(
CLIENT_GET,
new RowMapper<Client>() {
public Client mapRow(ResultSet rs, int rowNum) throws SQLException {
Client client = new ClientImpl(); // !! this (1)
client.setAccounts(new HashSet<Account>()); // !! this (2)
client.setId(rs.getInt(1));
client.setName(rs.getString(2));
return client;
}
},id
);
return clients.get(0);
}
and the following Spring wiring:
<bean id="account" class="client.AccountRON" scope="prototype">
<property name="currency" value = "RON" />
<property name="ammount" value="0" />
</bean>
<bean id="client" class="client.ClientImpl" scope="prototype">
<property name="name" value="--client--" />
<property name="accounts">
<set>
</set>
</property>
</bean>
The things is that i dont like the commented lines of java code (1) and (2).
I'm going to start with (2) which i think is the easy one: is there a way i can wire the bean in the .xml file to tell spring to instantiate a set implementation for the 'accounts' set in ClientImpl? so i can get rid of (2)
Now moving on to (1): what happens if the implementation changes ? do i really need to write another DAO for a different implementation? or do i have to construct a BeanFactory ? or is there another more beautiful solution ?
Thanks!
I'm a bit confused here - why have you defined a ClientImpl bean in your XML, but not using it in your Java?
Your already have most of the solution, you just need to fetch a new ClientImpl from Spring each iterations through the loop:
private #Autowired BeanFactory beanFactory;
public Client getClient(int id) {
List<Client> clients= getSimpleJdbcTemplate().query(
CLIENT_GET,
new RowMapper<Client>() {
public Client mapRow(ResultSet rs, int rowNum) throws SQLException {
Client client = beanFactory.getBean(Client.class);
client.setId(rs.getInt(1));
client.setName(rs.getString(2));
return client;
}
},id
);
return clients.get(0);
}
With this approach, the actual construction and initialization of ClientImpl is done by Spring, not your code.

Categories

Resources