Calling Stored Procedure using Spring Data JPA - spring

I want to know whether it is possible to call stored procedure using Spring Data JPA which is having resultset and multiple out parameter.
I found Git issue for same https://github.com/spring-projects/spring-data-examples/issues/80
If it is resolved, could someone provide one example with Spring Boot?

The way I've accomplished this in the past is to add custom behavior to a Spring Data JPA repository (link). Inside that I get the EntityManager and use java.sql.Connection and CallableStatement
Edit: Adding high level sample code. Sample makes the assumption that you are using Hibernate but idea should be applicable to others as well
Assuming you have an EntityRepository
public interface EntityRepositoryCustom {
Result storedProcCall(Input input);
}
public class EntityRepositoryImpl implements EntityRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public Result storedProcCall(Input input) {
final Result result = new Result();
Session session = getSession();
// instead of anonymous class you could move this out to a private static class that implement org.hibernate.jdbc.Work
session.doWork(new Work() {
#Override
public void execute(Connection connection) throws SQLException {
CallableStatement cs = null;
try {
cs = connection.prepareCall("{call some_stored_proc(?, ?, ?, ?)}");
cs.setString(1, "");
cs.setString(2, "");
cs.registerOutParameter(3, Types.VARCHAR);
cs.registerOutParameter(4, Types.VARCHAR);
cs.execute();
// get value from output params and set fields on return object
result.setSomeField1(cs.getString(3));
result.setSomeField2(cs.getString(4));
cs.close();
} finally {
// close cs
}
}
});
return result;
}
private Session getSession() {
// get session from entitymanager. Assuming hibernate
return em.unwrap(org.hibernate.Session.class);
}
}

Related

How to rollback child transaction if any exception in parent transaction?

I have two transaction manager for two database. I need to persist same data into both databases. If one transaction failed, other one need rollback. I have done like below
public interface DataService {
void saveData();
}
#Service
public class DataServiceImpl implements DataService {
#Autowired
private DataRepository dataRepository;
#Autowired
private OrDataRepository orDataRepository;
#Autowired
#Qualifier("orService")
private OrService orDataServiceImpl;
#Override
#Transactional(transactionManager = "transactionManager", rollbackFor = {RuntimeException.class})
public void saveData() {
Data data = new Data();
data.setCompKey(UUID.randomUUID().toString().substring(1,5));
data.setName("data");
dataRepository.save(data);
orDataServiceImpl.save();
//throw new RuntimeException("");
}
}
public interface OrService {
void save();
}
#Service("orService")
public class OrDataServiceImpl implements OrService {
#Autowired
private OrDataRepository orDataRepository;
#Override
#Transactional(rollbackFor = {RuntimeException.class})
public void save() {
OrData data = new OrData();
data.setCompKey(UUID.randomUUID().toString().substring(1,5));
data.setName("ordata");
orDataRepository.save(data);
}
}
I have two transaction manager (entityManager & orEntityManager) for two different DB.
If any exception in OrDataServiceImpl save method, data is not getting persisted in both DB. But if any exception in DataServiceImpl saveData method, data is getting persisted into OrData table.
I want to rollback the data from both DB if any exception.
chainedTransactionManager is deprecated. So can't use. atomikos and bitronix also can't use due to some restrictions. Kindly suggest better way to achieve distributed transation
The code need to be refactored, edit the DataServiceImpl.save() method.
Comment the orDataServiceImpl.save() line
public void saveData() {
Data data = new Data();
data.setCompKey(UUID.randomUUID().toString().substring(1,5));
data.setName("data");
dataRepository.save(data);
//orDataServiceImpl.save();
//throw new RuntimeException("");
}
Refactor/Edit the OrDataService Interface
public interface OrDataService {
void save(String uuid);
void delete(String uuid);
//will be use for compensating transaction
}
Update the OrDataServiceImpl class to implement above interface
Write new orchestration Method and use compensating transaction to rollback
pseudo code
call OrDataServiceImpl.save()
if step#1 was success
-> DataServiceImpl.saveData()
if Exception at step#3,
->OrDataServiceImpl.delete() [//to rollback]
else if, Exception at step#1
//do nothing

Why hibernate is saving values without transaction?

i'm using spring boot(2.1.4) with hibernate(5.3.9).
public class BaseDao{
#Autowired
private SessionFactory sessionFactory;
private Session session;
private Transaction transaction;
#Autowired
private EntityManagerFactory entityManagerFactory;
#PersistenceContext
private EntityManager entityManager;
public SessionFactory getSessionFactory() {
return sessionFactory;
}
public EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
public EntityManager getEntityManager() {
return entityManager;
}
public Session getSession() throws Exception{
if(session == null) {
session = getSessionFactory().openSession();
}
if(transaction == null) {
transaction = session.beginTransaction();
}
return session;
}
public void commit() throws Exception{
if(transaction != null) {
transaction.commit();
transaction = null;
}
if(session != null) {
session.close();
session = null;
}
}
public void rollback() throws Exception{
if(transaction != null) {
transaction.rollback();
transaction = null;
}
if(session != null) {
session.close();
session = null;
}
}
protected void save(Object object) throws Exception {
getSessionFactory().openSession().save(object); //saves data in db
getSession().save(object); //is not saving data
}
getSessionFactory().openSession().save(object); this code is saving data to db even without commit
getSession().save(object); this code required commit to be called as txn is created but not commited
hibernate log
i see below log for both the line of code
insert
into
TEST_ENTITY
(CREATED_BY, CREATED_DATE, ENABLED, LAST_MODIFIED_BY, LAST_MODIFIED_DATE, NAME)
values
(?, ?, ?, ?, ?, ?)
i have few questions on this behavior.
I know write operation will not happen without commit, so any idea what is wrong here or what causing commit in first scenario ?
Is it ok to use above code i.e. first scenario ?
If first approach is not right then do i need to create and commit txn for each object, any better approach so that even if i have to commit txn, i don't want to replicate the txn.commit() in every new method i write in BaseDao.java i.e. say i have create(), update(),delete() methods can i move this txn.commit() out of methods ?
Few places i'm using spring data jpa for fetching/saving record (given below), how txn is being handled in spring data jpa ? any references ?
#Repository
public interface TestEntitytRepo extends JpaRepository<TestEntity, Long> {
...
}
Please let me know if i missed any details to capture here.
Thanks in advance.
In hibernate, the Save() method stores an object into the database. It will Persist the given transient instance, first assigning a generated identifier. It returns the id of the entity created. When a session in hibernate is created using SessionFactory.openSession(), no transaction is created, so all the operations are executed outside of the transaction context !! In order to ensure the data gets saved into the database, a new transaction needs to be created.
I am a bit skeptical about the behaviors explained by you above. Seems like auto-commit option is enabled. If so, then this is not an issue as save() method is done, commit automatically happens backstage !!

Difference about Ajax and REST

I'm trying to understand a little problem I'm facing in a project that I'm working at the moment.
In my project, i'm the back end developer and I make some functions to be called by the front end with some Ajax functions. Today i'm facing a strange and random problem.
Whenever I use RESTful client to call my endpoints, it works pretty fine, without a single error in any of the individual endpoints. But when the front end calls them using ajax, it sometimes gets an error of "Can't operate on a closed Connection!" or "Can't operate on a closed Statement!", it's pretty random which one of them appears.
I'm using the following structure:
#Repository
public class RandomRepository {
#Autowired
private BasePersistence dao;
EntityManager em;
Session session;
public UserDTO getUserById(Long userId) {
if (userId == null)
return null;
em = BasePersistence.entityManagerFactory.createEntityManager();
em.getTransaction().begin();
session = em.unwrap(Session.class);
User user = session.doReturningWork(new ReturningWork<User>() {
#Override
public User execute(Connection con) throws SQLException {
try (PreparedStatement stmt = con.prepareStatement("SELECT * FROM tbl_users AS u WHERE id = "+ userId +";")) {
ResultSet rs = stmt.executeQuery();
User u = null;
while(rs.next()) {
u = new User(rs.getLong(1),rs.getString(2));
}
return u;
}
}
});
em.getTransaction().commit();
em.close();
return user != null ? new UserDTO(user) : null;
}
}
I use this same structure to make all my database operations.
I create the EntityManager > I begin the transaction > I unwrap the session > I execute a doWork or doReturningWork (depending of the type of query) > I commit > I close the EntityManager.
My question is, even by creating the EntityManager inside the method, could it be closed by another EntityManager created on another method like this by using Ajax to call the endpoints?
If that's the case, how could I prevent this problem?
Is this really a difference between the REST call and the AJAX call?

Spring #Transational no rollback for jdbctemplate.update(PreparedStatementCreator, KeyHolder)

I'm newbie with spring framework. I used spring.xml to define the datasouce and DataSourceTransactionManager, so that I could insert data with jdbctemplate object.
And now I want to add the rollback to the transaction.
Unfortunately this rollback only works for JdbcTemplate.updata (String SQL), not for JdbcTemplate.update(PreparedStatementCreator, Keyholder), which I used to get the generated ID by insert.
#Override
#Transactional("txManagerTest")
public SQLQueryObjectIF process(SQLQueryObjectIF queryObj) {
KeyHolder keyHolder = new GeneratedKeyHolder();
for (final String query : queryObj.getQueries()) {
System.out.println(query);
// Rollback works fine for the "update" below.
//jdbcTemplate.update(query);
// Rollback doesn't work for the "update" below. Don't why...
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
jdbcTemplate.getDataSource().getConnection().setAutoCommit(false);
PreparedStatement ps = jdbcTemplate.getDataSource().getConnection().prepareStatement(query,Statement.RETURN_GENERATED_KEYS);
return ps;
}
}, keyHolder);
//log.info(keyHolder.getKeys().toString());
}
//just for rollback test
if (keyHolder.toString().length()>-1){
throw new RuntimeException("Test Error");
}
return queryObj;
}
That code should be used like this (you need to use the connection given as parameter), otherwise with your code you will get a connection that Spring doesn't know about, by directly accessing the DataSource instance (if Spring doesn't know about it, it will not know to rollback in case of exception):
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(query,Statement.RETURN_GENERATED_KEYS);
return ps;
}

Can't create an AbstractRoutingDataSource that needs some data from another database

We currently have an application which uses multiple databases with the same schema. At the moment we're using a custom solution for switching between them based on the user's session. This works via
public final class DataSourceProxy extends BasicDataSource {
...
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
String targetUrl = details.get("database");
Connection c = super.getConnection();
Statement s = c.createStatement();
s.execute("USE " + targetUrl + ";");
s.close();
return c;
} else {
return super.getConnection();
}
}
Now we want to build a solution using AbstractRoutingDataSource. The problem is:
#Component
public class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
Environment env;
#Autowired
DbDetailsRepositoy repo;
public CustomRoutingDataSource() {
Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
for(DBDetails dbd : repo.findAll() {
// create DataSource and put it into the map
}
setTargetDataSources(new HashMap<Object, Object>());
}
#Override
protected Object determineCurrentLookupKey() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.getDetails() instanceof Map) {
Map<String, String> details = (Map<String, String>) auth.getDetails();
return details.get("database");
}
return null;
}
}
Inside the constructor (or even via #PostConstruct) we have to fill the targetDataSources Map. But(!) for this we need the connection details which are stored in another database, which has its own DataSource and Entity Manager.
It seems like Spring can't determine the order of Bean construction, or maybe I'm just missing something. It always gives a NullPointerException when accessing the repository (which btw is a JpaRepository).
We're using Spring 3.2.3, Spring Data, Hibernate 4.2. Complete Annotation and Java-Code configuration of Spring and Spring Security.
Please help us!
Spring of course has to call the constructor before it can populate the properties. But that's not a Spring thing, that's basic Java 101 and one of the plenty downsides of using field injection.
To avoid this, simply add your dependencies to the constructor:
#Component
class CustomRoutingDataSource extends AbstractRoutingDataSource {
#Autowired
public CustomRoutingDataSource(DbDetailsRepository repo, Environment environment) {
…
}
…
}

Resources