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?
Related
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 !!
I have a strage(for me) question to ask. I have created synchronized Service which is called by Controller:
#Controller
public class WebAppApiController {
private final WebAppService webApService;
#Autowired
WebAppApiController(WebAppService webApService){
this.webApService= webApService;
}
#Transactional
#PreAuthorize("hasAuthority('ROLE_API')")
#PostMapping(value = "/api/webapp/{projectId}")
public ResponseEntity<Status> getWebApp(#PathVariable(value = "projectId") Long id, #RequestBody WebAppRequestModel req) {
return webApService.processWebAppRequest(id, req);
}
}
Service layer is just checking if there is no duplicate in request and store it in database. Because client which is using this endpoint is making MANY requests continously it happened that before one request was validated agnist duplicate other the same was put in database - that is why I am trying to do synchronized block.
#Service
public class WebAppService {
private final static String UUID_PATTERN_TO = "[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}";
private final WebAppRepository waRepository;
#Autowired
public WebAppService(WebAppRepository waRepository){
this.waRepository= waRepository;
}
#Transactional(rollbackOn = Exception.class)
public ResponseEntity<Status> processScanWebAppRequest(Long id, WebAppScanModel webAppScanModel){
try{
synchronized (this){
Optional<WebApp> webApp=verifyForDuplicates(webAppScanModel);
if(!webApp.isPresent()){
WebApp webApp=new WebApp(webAppScanModel.getUrl())
webApp=waRepository.save(webApp);
processpropertiesOfWebApp(webApp);
return new ResonseEntity<>(HttpStatus.CREATED);
}
return new ResonseEntity<>(HttpStatus.CONFLICT);
}
} catch (NonUniqueResultException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} catch (IncorrectResultSizeDataAccessException ex){
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
}
}
Optional<WebApp> verifyForDuplicates(WebAppScanModel webAppScanModel){
return waRepository.getWebAppByRegex(webAppScanModel.getUrl().replaceAll(UUID_PATTERN_TO,UUID_PATTERN_TO)+"$");
}
And JPA method:
#Query(value="select * from webapp wa where wa.url ~ :url", nativeQuery = true)
Optional<WebApp> getWebAppByRegex(#Param("url") String url);
processpropertiesOfWebApp method is doing further processing for given webapp which at this point should be unique.
Intended behaviour is:
when client post request contains multiple urls like:
https://testdomain.com/user/7e1c44e4-821b-4d05-bdc3-ebd43dfeae5f
https://testdomain.com/user/d398316e-fd60-45a3-b036-6d55049b44d8
https://testdomain.com/user/c604b551-101f-44c4-9eeb-d9adca2b2fe9
Only first one will be stored within database but at this moment this is not what is happening. Select from my database:
select inserted,url from webapp where url ~ 'https://testdomain.com/users/[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$';
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
2019-11-07 08:53:05 | https://testdomain.com/users/d398316e-fd60-45a3-b036-6d55049b44d8
(3 rows)
I will try to add unique constraint on url column but I can't imagine this will solve the problem while when UUID changes new url will be unique
Could anyone give me a hint what I am doing wrong?
Question is related with the one I asked before but not found proper solution, so I simplified my method but still no success
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);
}
}
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;
}
I have configured an application to work with MyBatis-Spring and I would like to connect to multiple databases.
For this purpose, in my applicationContext.xml I have defined one datasource, one Transaction Manager (org.springframework.jdbc.datasource.DataSourceTransactionManager), one Sql Session Factory (org.mybatis.spring.SqlSessionFactoryBean) and one MapperScannerConfigurer (org.mybatis.spring.mapper.MapperScannerConfigurer) for each one of them.
Then, inside my service class I would like to perform CRUD operations with multiple databases inside the same method. As I must point to the correct transaction manager I have done what is commented below:
#Service("myServiceDB")
public class MyServiceDB implements MyService {
[...]
#Transactional(value = "TransactionManager1", rollbackFor = MyCustomException.class)
public MyUser multipleMethod(final int idUser) throws MyCustomException {
MyUser myUser = null;
int rowsAffected1 = -1;
int rowsAffected2 = -1;
try {
myUser = this.mapperOne.getById(idUser);
if (myObject != null) {
rowsAffected1 = this.mapperOne.deleteByIdUser(idUser);
}
if (rowsAffected1 == 1) {
insertUser(myUser);
}
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
if ((myUser == null) || (rowsAffected1 != 1)) {
throw new MyCustomException(TRANSACTION_ERROR);
}
return myUser;
}
#Transactional(value = "TransactionManager2", rollbackFor = MyCustomException.class)
public void insertUser(final MyUser myUser) throws MyCustomException{
int rowsAffected = -1;
try {
rowsAffected = this.mapperTwo.insert(myUser);
**throw new MyCustomException();**
} catch (DataAccessException dae) {
throw new MyCustomException(TRANSACTION_ERROR, dae);
}
//if (rowsAffected != 1) {
// throw new MyCustomException(TRANSACTION_ERROR);
//}
}
[...]
}
So each method points to its corresponding transaction manager.
If I throw the custom exception in the second method after the insert, I get the delete made in the first method correctly rolled back. However, the insert performed by the second Transaction Manager is not rolled back properly as I would desire. (i.e. the user is inserted in the second database but not deleted in the first one).
My questions are:
Is it possible to achieve what I want?
How should I configure the #Transactional annotation?
Thanks in advance.
I found the solution here by #RisingDragon:
"If you are calling it from another local method then it will not work, because spring has no way of know that it is called and to start the transaction.
If you are calling it from a method of another class by using autowired object of the class which contains insertNotes() method, then it should work."
In my case, I created a second class (e.g. RisingDragom´s NoteClass) with some #Transactional methods (e.g. insertUser in my code) and then, the rollback worked!! This second class appeared in the debugger with the tail "$$EnhancedByCGLib".
However, if you need a method with several steps in different databases another "custom" rollback should be applied...The rollback is just applied method by method, not for the full process, so surely some data should be restored "by hand" in case of failure in any of the steps.