I want to create an exception log in the database when an #Async operation fails with an exception.
You can see the implementation for AsyncExecutorConfiguration and AsyncExceptionHandler classes below.
Inside AsyncExceptionHandler class, when I call a service that tries to access the database, I am getting: org.hibernate.HibernateException: Could not obtain transaction-synchronized Session for current thread
#Configuration
#EnableAsync
public class AsyncExecutorConfiguration implements AsyncConfigurer {
#Autowired
private AsyncExceptionHandler asyncExceptionHandler;
private static final int CORE_POOL_SIZE = 3;
private static final int MAX_POOL_SIZE = 3;
private static final int QUEUE_CAPACITY = 24;
private static final String THREAD_NAME_PREFIX = "AsynchThread-";
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return asyncExceptionHandler;
}
}
#Component
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
#Autowired
private NotificationService notificationService;
#Override
#Transactional(rollbackFor = Exception.class, readOnly = false)
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
AsyncErrorLog log = new AsyncErrorLog(ex);
notificationService.saveLogAndNotify(log); // throws exception "Could not obtain transaction-synchronized Session for current thread"
}
}
#Service
public class MyServiceImpl implements MyService {
#Override
#Async
#Transactional(rollbackFor = Exception.class, readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void doSomething(Long id) {
// I can execute database operations here
}
...
#Async function itself already has a valid session. What should I do to have a valid session in AsyncExceptionHandler class too?
--
UPDATE
Here is the simplified implementations for NotificationServiceImpl and LogDaoImpl.class where we get the error.
#Service
public class NotificationServiceImpl implements NotificationService {
#Autowired
private LogDao logDao;
#Override
#Transactional(rollbackFor = Exception.class, readOnly = false)
public void saveLogAndNotify(Log log) {
return logDao.createLog(log);
}
#Repository
public class LogDaoImpl{
#Autowired
protected SessionFactory sessionFactory;
#Override
public void createLog(Log log) {
sessionFactory.getCurrentSession().saveOrUpdate(log);
}
Per the Hibernate exception; if you're not using Spring Data, you'll have to make sure the notification service explicitly invokes the database calls on the Hibernate session.
On another note, in my experience, the main use cases for the UncaughtExceptionHandler (in general) are used for:
A simple last-resort to handle RuntimeExceptions that may be unknown to the programmer that for some reason cannot (or are not) caught in code
A way to catch exceptions in code that the programmer has no control over (e.g. if you're invoking Async directly on some third party library, etc.)
The commonality between the two is that this Handler is used for something unexpected. In fact, Spring itself accounts for the "unexpectedness" in your own code and Spring Async already sets a default one for you that will log to the console (code here), letting you not have to worry about rogue exceptions killing threads and not knowing why. (Note: The message in the source code says it's catching an "unexpected" exception. Of course exceptions are unexpected, but these are one's that you really didn't know could happen. Spring Async will log it for you.)
That being the case, in your example, since you're doing Spring Database operations and should know exactly what's happening inside of #doSomething, I would just go with removing the AUEH a try-catch (and/or -finally) and handle the exception inside of #doSomething:
#Service
public class MyServiceImpl implements MyService {
// Self autowired class to take advantage of proxied methods in the same class
// See https://stackoverflow.com/questions/51922604/transactional-and-stream-in-spring/51923214#51923214
private MyService myService;
private NotificationService notificationService;
#Override
#Async
public void doSomething(Long id) {
// I can execute database operations here
try {
myService.doDatabaseOperations(...);
} catch(DatabaseAccessException e) {
AsyncErrorLog log = new AsyncErrorLog(ex);
notificationService.saveLogAndNotify(log);
}
// Other exceptions (from DB operations or the notifications service) can be
// handled with other "catches" or to let the SimpleAsyncExHandler log them for you.
// You can also use standard multithreading exception handling for this
}
#Transactional(rollbackFor = Exception.class, readOnly = false, propagation = Propagation.REQUIRES_NEW)
public void doDatabaseOperations(...) {
...
}
}
You can use the applicationContext in your handler to lookup the notificationService. I had the same issue when I used #Autowired for the handler, which in turn injected my LogService. After looking at the logs I saw that the TransactionSynchronizationManager is clearing transaction synchronization after the rollback of the exception and nothing else except the no transaction for ...... error.
After using the applicationContext for looking up the logService bean and changing my handler, I saw the desired result in the logs.
begin
Initializing transaction synchronization
Getting transaction for [....AsyncService.doAsync]
Exception
rolling back
Clearing transaction synchronization
begin
Initializing transaction synchronization
Getting transaction for [.....LogService.save]
Change your config to include the interface ApplicationContextAware which will give you a convenience method to access the applicationContext. Set it as a instance variable.
See my configuration class below.
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer, ApplicationContextAware {
private static final int CORE_POOL_SIZE = 3;
private static final int MAX_POOL_SIZE = 3;
private static final int QUEUE_CAPACITY = 24;
private static final String THREAD_NAME_PREFIX = "AsynchThread-";
private ApplicationContext applicationContext;
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(CORE_POOL_SIZE);
executor.setMaxPoolSize(MAX_POOL_SIZE);
executor.setQueueCapacity(QUEUE_CAPACITY);
executor.setThreadNamePrefix(THREAD_NAME_PREFIX);
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new AsyncExceptionHandler(this.applicationContext);
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
I have removed the #Component from the handler and use it as a POJO.
Every time getAsyncUncaughtExceptionHandler is called with an exception, a new handler instance is created with the applicationContext as a dependency.
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
private final ApplicationContext applicationContext;
public AsyncExceptionHandler(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void handleUncaughtException(Throwable ex, Method method, Object... params) {
Log log = new Log();
log.setEntry(ex.getMessage());
LogService logService = this.applicationContext.getBean(LogService.class);
logService.save(log);
}
}
The save method on logService requires a new transaction every time it is called.
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Log log)
This will help you:
#Override
public void createLog(Log log) {
try {
session = sessionFactory.getCurrentSession();
} catch (HibernateException e) {
session = sessionFactory.openSession();
}
session.saveOrUpdate(log);
}
Related
I am trying to use the #Async capabilities of the Spring framework to perform a simple indexing task.
The problem I'm facing is that I feel that the EntityManager used in my Async function is somehow reused from previous calls so my data is not up to date and sometimes uses old data.
Here is the code I wrote as an example. The goal is to update a product's data and index it asynchronously after I publish an event using Spring's ApplicationEventPublisher:
ProductService
#Service
class ProductService {
private final EntityManager entityManager;
private final ApplicationEventPublisher eventPublisher;
#Autowired
public ProductService(EntityManager entityManager, ApplicationEventPublisher eventPublisher) {
this.entityManager = entityManager;
this.eventPublisher = eventPublisher;
}
#Transactional
public void patchProduct (String id, ProductDto productDto) {
Product product = this.entityManager.find(Product.class, id);
product.setLabel(productDto.getLabel());
this.entityManager.flush();
this.eventPublisher.publishEvent(new ProductEvent(product, ProductEvent.EVENT_TYPE.UPDATED));
}
}
EventListener
#Component
public class ProductEventListener {
private final AsyncProcesses asyncProcesses;
#Autowired
public ProductEventListener (
AsyncProcesses asyncProcesses
) {
this.asyncProcesses = asyncProcesses;
}
#EventListener
public void indexProduct (ProductEvent productEvent) {
this.asyncProcesses.indexProduct(productEvent.getProduct().getPok());
}
}
AsyncProcesses
#Service
public class AsyncProcesses {
private final SlowProcesses slowProcesses;
#Autowired
public AsyncProcesses(SlowProcesses slowProcesses) {
this.slowProcesses = slowProcesses;
}
#Async
public void indexProduct (String id) {
this.slowProcesses.indexProduct(id);
}
}
SlowProcesses
#Service
public class SlowProcesses {
private EntityManager entityManager;
private ProductSearchService productSearchService;
#Autowired
public SlowProcesses(EntityManager entityManager, NewProductSearchService newProductSearchService) {
this.entityManager = entityManager;
this.newProductSearchService = newProductSearchService;
}
#Transactional(readonly = true)
public void indexProduct (String pok) {
Product product = this.entityManager.find(Product.class, pok);
// this.entityManager.refresh(product); -> If I uncomment this line, everything works as expected
this.productSearchService.indexProduct(product);
}
}
As you can see on the SlowProcesses file, if I refresh the product object in the entityManager, I get the correct and up to date data. If I do not, I might get old data from previous calls.
What is the correct way to use the EntityManager in an Asynchronous call? Do I really have to refresh all my objects in order to make everything work? Am I doing something else wrong?
Thank you for reading through
Since instances of EntityManager are not thread-safe as pointed out by Jordie, you may want to try this instead:
Instead of injecting an EntityManager, inject an EntityManagerFactory. Then from the EntityManagerFactory retrieve a new EntityManager instance that is used only for the duration of the method in question.
I'm using Spring Boot with Solr.
I can see Spring-tx in my resolved dependencies.
When I call a #Transactional annotated method in a Spring bean,
a) at runtime, i don't see any signs of it being wrapped in any transaction management proxy, and
b) when the method throws a RuntimeException the data is not rolledback.
The email/phone repositories and just interfaces that extend
org.springframework.data.solr.repository.SolrCrudRepository
What am i missing?
At have the method annotated #Transactional in both the interface and the implementation, just in case ;-)
public interface MyServiceInterface {
#Transactional
public CreateDetailsResponse createAllDetails(
final CreateDetailsRequest createDetailsRequest) throws BusinessException;
}
public class MyService implements MyServiceInterface {
#Autowired
private EmailRepository emailRepository;
#Autowired
private EmailRepository phoneRepository;
#Transactional
public CreateDetailsResponse createAllDetails(
final CreateDetailsRequest createDetailsRequest) throws BusinessException {
//method below calls emailRepository.save(emailDocument)
saveEmail();
//method below is supposed to call phoneRepository.save(phoneDocument)
//but throws RuntimeException before save is called
savePhone();
}
//...
}
#Configuration
#EnableSolrRepositories(basePackages = "com.....repository", multicoreSupport = true,
considerNestedRepositories = true, repositoryBaseClass = SoftCommitSimpleSolrRepository.class)
#EnableAutoConfiguration
public class ConsumersServiceConfiguration {
//...
#Bean
public MyServiceInterface myService() {
return new MyService();
}
}
Supposedly Solr supports transactions:
My service should save data to a parent and child database tables, and rollback when an error ocurrs. I've tried forcing an error, using a hardcoded RuntimeException, and found the transaction gets commited no matter what.
What I'm I missing?
I'm using Spring Boot 2, including the spring-boot-starter-jdbc dependency.
Database is Oracle 11g.
Main configuration:
#SpringBootApplication
#EnableTransactionManagement
public class MyApplication extends SpringBootServletInitializer{
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
Service layer:
#Service
public class MyBean {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final MyDAO myDao;
#Autowired
public MyBean (NamedParameterJdbcTemplate jdbcTemplate, MyDAO myDao) {
this.jdbcTemplate = jdbcTemplate;
this.myDao= myDao;
}
#Override
#Transactional
public void saveData(...){
myDao.saveData(jdbcTemplate, ...);
}
}
DAO:
public void saveData(jdbcTemplate, ...){
saveDataInParentDatatable(jdbcTemplate, ...);
saveDataInChildDatatable(jdbcTemplate, ...);
}
private void saveDataInChildDatatable(jdbcTemplate, ...){
throw new RuntimeException();
}
I faced a similar issue. I'm assuming that you were calling the MyBean.saveData method at MyBean's another method.
After searching, trying and failing a lot, I found this link: http://ignaciosuay.com/why-is-spring-ignoring-transactional/
In it, it's explained that when the invoked method is in the same class where is it invoked, the #Transactional annotation is ignored. The Spring explanation for it is:
“In proxy mode (which is the default), only external method calls
coming in through the proxy are intercepted. This means that
self-invocation, in effect, a method within the target object calling
another method of the target object, will not lead to an actual
transaction at runtime even if the invoked method is marked with
#Transactional. Also, the proxy must be fully initialized to provide
the expected behaviour so you should not rely on this feature in your
initialization code, i.e. #PostConstruct.”
So I created another class to encapsulate my DAO method calling, used its method instead and it worked.
So for this case, it could be something like:
MyBean:
#Service
public class MyBean {
MyBean2 bean2;
public void saveData(...){
bean2.saveData(jdbcTemplate, ...);
}
}
MyBean2:
#Service
public class MyBean2 {
private final NamedParameterJdbcTemplate jdbcTemplate;
private final MyDAO myDao;
#Autowired
public MyBean2 (NamedParameterJdbcTemplate jdbcTemplate, MyDAO myDao) {
this.jdbcTemplate = jdbcTemplate;
this.myDao= myDao;
}
#Override
#Transactional
public void saveData(...){
myDao.saveData(jdbcTemplate, ...);
}
}
try this:
#Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
recommended use:
#Transactional(rollbackFor = {Exception.class, RuntimeException.class})
I have a service implementation with a particular method like so:
public class ExampleServiceImpl implements ExampleService {
#AutoWired
#Resource
private RecordRepository recordRepository;
private void processRecord() {
// some code here
}
#Transactional(readOnly=false)
public void processRecord(Record a) {
Record original = getOriginal(a);
recordRepository.saveChanges(a,original);
}
}
Where the Record class is the root object of an object graph. RecordRepository looks something like the following with sub repositories to save various children of the objects in the graph.
public class RecordRepository extends BaseRepository<Record> {
#AutoWired
#Resource
private IDao databaseDao;
#AutoWired
#Resource
private SubRecordRepository subRecordRepository;
public void saveChanges(Record a, Record b) {
//Perform some processing on a, b
for(SubRecord subA : a.getSubRecords()) {
subRecordRepository.saveChanges(subA);
}
databaseDao.updateRecord(a);
}
}
public class DatabaseDao extends NamedParameterJdbcDaoSupport implements IDao {
#Autowired
public DatabaseDao(#Qualifier("org.somewhere.Datasource") DataSource ds) {
super();
this.setDataSource(ds);
}
public void updateRecord(Record inRecord) {
String query = (String) sql.get("updateRecord");
SqlParameterSource parms = new BeanPropertySqlParameterSource(inRecord);
getNamedParameterJdbcTemplate().update(query, parms);
}
public void insertSubRecord(SubRecord inSubRecord) {
String query = (String) sql.get("insertSubRecord");
SqlParameterSource parms = new BeanPropertySqlParameterSource(inSubRecord);
getNamedParameterJdbcTemplate().insert(query, parms);
}
// other update and insert methods
}
Will the transaction be applied across all involved inserts\updates from the processRecord call? In other words, if an insert or update fails, will all previously called inserts and updates from ExampleServiceImpl.processRecord get rolled back?
Yes. The transactional aspect makes sure that a transaction is started before the annotated method is called, and that the transaction (if started by this method) is committed or rollbacked once the method returns.
The transactional interceptor doesn't know (and doesn't care) about which other methods are called inside the annotated method. Every read and write to the DataSource handled by the Spring transaction manager will be included in the same transaction.
I am having a bean within which I create a new Thread with Runnable:
#Component
public class MyBean {
private final Task task = new Task();
#PersistenceContext
EntityManager em;
#PostConstruct
public void init() {
task.setEntityManager(em);
new Thread(task).start();
}
public static class Task implements Runnable {
#Setter
private EntityManager em;
#Override
public void run() {
while (true) {
// working with EntityManager
Thing t = em.findById(...); // Fetching a Thing from repo
t.getSomethingList(); // LazyInit exception
wait();
}
}
}
}
Withing the init method, new Thread is created with instance of EntityManager. When I try to load something from the repository the session is instantly closed and getting any lazy field results in failed to lazily initialize a collection of role: Something, no session or session was closed exception from Hibernate.
I tried all the #Transactional annotations with no effect. I need to achieve something like OpenEntityManagerInView, but with the difference that this is not within view.
Thanks
EDIT1:
According to comments - I tried using em.getTransaction().begin(); but this is getting me Not allowed to create transaction on shared EntityManager - use Spring transactions or EJB CMT.
skirsch suggested that I should invoke Transactional method on some other bean. That is what I actually do - exactly as you suggested. I wanted to make things simpler and I did not realize the difference, so I demostrated the problem directly in the class Task. So to summarize, I have it exactly like skirsch suggested, but the problem persists.
As Spring is not managing your Runnable, annotating it won't have the desired effect. So you either need to use an annotated (and Spring-managed) bean from within your Runnable or you need to take care of the txn management manually.
Use Spring transaction management
You define some kind of service
#Service
public class MyService {
#PersistenceContext
EntityManager em;
#Transactional
public void doSomething() {
Thing t = em.findById(...);
t.getSomethingList();
}
}
And then your bean would look like this:
#Component
public class MyBean {
private final Task task = new Task();
#Autowired
MyService service;
#PostConstruct
public void init() {
task.setService(service);
new Thread(task).start();
}
public static class Task implements Runnable {
#Setter
private MyService service;
#Override
public void run() {
while (true) {
service.doSomething();
wait();
}
}
}
}
Manual transaction management
In case you set up JPA Resource Local Transactions, here you go:
// working with EntityManager
em.getTransaction().begin()
try {
Thing t = em.findById(...);
t.getSomethingList();
} finally {
em.getTransaction().rollback()
}
wait();