How to convert jee transactions to spring transactions - spring

I have a code snippet that needs to be converted to spring data jpa, as below.
The code uses EntityManager to get current transaction, if it is active then rollbacks the transaction. If not then creates a new transaction and persists the records.
EntityTransaction transaction = entityManager.getTransaction();
if (transaction.isActive()) {
transaction.rollback();
}
transaction.begin();
for (Iterator<AuditEntity> it = auditLogs.iterator(); it.hasNext();) {
AuditEntity enquiry = it.next();
entityManager.persist(enquiry);
entityManager.flush();
entityManager.clear();
}
transaction.commit();
How can I make this be using string data jpa and hibernate?

// Create repository bean, spring will generate implementation
interface AditLogRepository extends CrudRepository<AuditEntity, Long> {}
// Inject repository bean, spring will do it automatically on startup
#Autowired AditLogRepository repository;
// Use Transactional annotation, spring will begin transaction, commit or rolback in case of RuntimeException
#Transactional
void saveAll(Collection<AuditEntity> auditLogs) {
for (AuditEntity entity : auditLogs) {
repository.save(entity);
}
}

Related

What is the correct way to ensure OpenEntityManagerInViewFilter closes an EntityManager session for a StreamingResponseBody?

For synchronous requests, OpenEntityManagerInViewFilter appears to close the EntityManager correctly.
For asynchronous requests such as when a REST controller returns a StreamingResponseBody, OpenEntityManagerInViewFilter appears to rely upon AsyncRequestInterceptor to close the EntityManager.
The AsyncRequestInterceptor however, will only close the EntityManager if an error or timeout occurs, which in my application, results in a HikariCP connection leak.
To work around this problem, I have created a copy of AsyncRequestInterceptor and modified its closeSession() method so that it unconditionally closes the JPA session:
private void closeSession() {
// if (this.timeoutInProgress || this.errorInProgress) {
// logger.debug("Closing Hibernate Session after async request timeout/error");
SessionFactoryUtils.closeSession(this.sessionHolder.getSession());
// }
}
What is the correct way to ensure that the EntityManager is closed, when processing is Async, i.e. when returning a StreamingResponseBody?
NOTE: I have tried explicitly closing an injected instance of the EntityManager, but the JPA connection remains active in the HikariCP:
#Transactional
#RestController
public class MyController {
#PersistenceContext
private final EntityManager entityManager;
#GetMapping(path = "/stream", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public StreamingResponseBody stream() {
return outputStream -> {
try {
// perform read-only operations with the entity manager
} finally {
entityManager.close();
}
};
}
}

Spring Boot Transaction support using #transactional annotation not working with mongoDB, anyone have solution for this?

Spring Boot version - 2.4.4,
mongodb version - 4.4.4
In my project, I want to do entry in 2 different document of mongodb, but if one fails than it should do rollback. mongodb supports transaction after version 4.0 but only if you have at least one replica set.
In my case I don't have replica set and also cannot create it according to my project structure. I can't use transaction support of mongodb because no replica-set. So, I am using Spring Transaction.
According to spring docs, to use transaction in Spring Boot, you only need to use #transactional annotation and everything will work(i.e. rollback or commit).
I tried many things from many sources but it is not rollbacking transaction if one fail.
Demo code is here,
This is demo code, not actual project.
This is my service class.
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
UserDetailRepository userDetailRepository;
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<JsonNode> createUser(SaveUserDetailRequest saveUserDetailRequest) {
try {
User _user = userRepository.save(new User(saveUserDetailRequest.getId(), saveUserDetailRequest.getFirstName(), saveUserDetailRequest.getLastName()));
UserDetail _user_detail = userDetailRepository.save(new UserDetail(saveUserDetailRequest.getPhone(), saveUserDetailRequest.getAddress()));
} catch (Exception m) {
System.out.print("Mongo Exception");
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Also tried below code but still not working,
#EnableTransactionManagement
#Configuration
#EnableMongoRepositories({ "com.test.transaction.repository" })
#ComponentScan({"com.test.transaction.service"})
public class Config extends AbstractMongoClientConfiguration{
private com.mongodb.MongoClient mongoClient;
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
#Bean
public com.mongodb.MongoClient mongodbClient() {
mongoClient = new com.mongodb.MongoClient("mongodb://localhost:27017");
return mongoClient;
}
#Override
protected String getDatabaseName() {
return "test";
}
}
The transaction support in Spring is only there to make things easier, it doesn't replace the transaction support for the underlying datastore being used.
In this case, it will simply delegate the starting/committing of a transaction to MongoDB. WHen using a database it will eventually delegate to the database etc.
As this is the case, the pre-requisites for MongoDB still need to be honoured and you will still need a replica.

jhipster perform a envers query from a service by reusing the database session

I would like to create a service which will perform a auditquery with envers.
After trying to find informations I used this code:
#Service
#Transactional
public class SecurityAuditService {
private final Logger log = LoggerFactory.getLogger(SecurityAuditService.class);
#PersistenceContext(type = PersistenceContextType.EXTENDED)
EntityManager entityManager;
public String findAll() {
List query = AuditReaderFactory.get( entityManager )
.createQuery()
.forRevisionsOfEntity(MyClass.class, false, true)
.setFirstResult(4)
.setMaxResults(2)
.getResultList();
But I have a java null exception with entityManager when trying it. How I could use the existing database session to perform my query?
Thanks,
Alain
entityManager field is not not injected by Spring, add it to SecurityAuditService's constructor or annotate it with #Autowired

how to keep jpa session in the thread

Now I use JPA in my web application. And I have a class like this:
class A {
...
#OneToMany
private List<B> list;
...
}
When it is in a HTTP request, I can use a.getList() successful. But in a schedule thread, it throws the exception:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: A.list, could not initialize proxy - no Session
Can I keep the session in the schedule thread just like the http request thread?
Actually, when spring handle the http request, it start the transaction by the interceptor org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor or the filter org.springframework.orm.hibernate3.support.OpenSessionInViewFilter. So if we want to keep the session, we can start and commit the transaction by our self.
Here is the code:
public class BackendThread {
#Autowired
private PlatformTransactionManager platformTransactionManager;
#Override
public void onMessage(Message message, byte[] pattern) {
new TransactionTemplate(platformTransactionManager).execute(new TransactionCallback<Void>() {
#Override
public Void doInTransaction(TransactionStatus transactionStatus) {
...
your code here
...
return null;
}
});
}
}
TransactionTemplate is helpful if you use JPA. I didn't use it and created a session/transactions on my own in background thread, so that each task had its own environment:
Inject entity manager or get it from Spring context or pass it as reference:
#PersistenceContext
private EntityManager entityManager;
Then create a new entity manager, to avoid using a shared one:
EntityManager em = entityManager.getEntityManagerFactory().createEntityManager();
Now you can start transaction and use Spring DAO, Repository, JPA, etc
private void save(EntityManager em) {
try
{
em.getTransaction().begin();
<your database changes>
em.getTransaction().commit();
}
catch(Throwable th) {
em.getTransaction().rollback();
throw th;
}
}

EJB with spring : transaction issue in JPA flush

I have an issue with an injected EntityManager in my MessageDriven Bean that use spring bean as services (the bootstrap is done with the SpringBeanAutowiringInterceptor.
Here is the code :
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
} catch (Exception e) {
// Handle error.
}
}
The process service has a DAO where the EntityManager is injected with the annotation #PersistentContext...
The problem is that if a JPA error occurs in the processService, it may occur during the entityManager.flush() call... so the try catch block is gone and the //Handle error stuff is not done.
So I tried to add manually the flush.
#MessageDriven(name = "ProcessMDB")
#Interceptors(SpringBeanAutowiringInterceptor.class)
public class ProcessMDB implements MessageListener {
#Autowired
private ProcessService processService;
#PersistenceContext
private EntityManager em;
#Override
public void onMessage(Message message) {
try {
id = message.getLongProperty("ID");
processService.process(id);
em.flush();
} catch (Exception e) {
// Handle error.
}
}
But it seems that the flush has no effect.
I've try to add the em.flush in the underlying DAO (just after the persist for instance) and it works! The exception is well raised and the catch block is executed. But it doesn't work if I put the em.flush() at the MessageDrivenBean level.
I think that it's transaction manager problem... The entitymanager in spring beans is not in the same tx than the injected entity manager in my ejb.
If I make em.find in the onMessage() method, the fetched object holds old values (the one in the database), not values that are changed in the service method.
I've configured my database as followed :
<jee:jndi-lookup id="emf" jndi-name="persistence/PUnit" />
and my tx manager as followed :
<tx:annotation-driven/>
<tx:jta-transaction-manager />
What do I wrong?
Can someonee help me?
Thanks
Stéphane
You have injected EntityManager in ProcessMDB, but the object is being persisted in ProcessService.
Now, how can any operation in ProcessMDB can affect ProcessService, both will have probably their own individual EntityManager.
Scope of PersistenceContext is upto associated EntityManager. The object will be in the context of ProcessService & not in ProcessMDB, therefore calling flush on later will have no effect & this is expected behaviour.

Resources