Hi I am trying to log exception into the DB
public void a(){
try{
String c = b();
}catch (Throwable ex){
com.log.Logger.log(ex);
throw new UserDefinedException(ex);
}
}
public String b(){
throw new NullPointerException("Transaction Logger")
}
I have a LoggerImpl class which logs the details of the exception into DB.
Only the UserDefinedException is getting logged where as the Null Pointer Exception is not. Could any one Plz help me.
LogEntry.java
private long id;
private String desc;
// getters and setters
Logger.java
public long log(Throwble ex){
LogEntry entry = new LogEntry();
entry.setDesc(ex.getMessage());
LoggerImpl log = new LoggerImpl();
log.insertLog(entry);
return entry.getId();
}
LoggerImpl.java
#Transactional(propogation = PROPOGATION.REQUIRES_NEW)
public void insertLog(LogEntry log){
insert.update(//fields);
}
Id is generated using a Sequence Incrementer
I am using JTA Transaction Manager.
You got such results because in your case LoggerImplementation class isn't managed by Spring container and container doesn't start new transaction for insertLog(..) method (as it supposed to be). To make this class managed you should inject it in your bean. I propose you to make such refactoring. This will work.
Instead of having Logger and LoggerImpl classes create Logger interface and LoggerImpl class that implements this interface.
public interface Logger {
long log(Throwable ex);
}
#Transactional
#Component
public final class LoggerImpl implements Logger {
#PersistenceContext
private EntityManager entityManager;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public long log(Throwable ex) {
LogEntry entry = new LogEntry();
entry.setDescription(ex.getMessage());
entityManager.persist(entry);
return entry.getId();
}
}
And then simply inject your exceptions Logger into necessary service class. For example:
#Transactional(rollbackFor=Exception.class)
#Service
public class TestService {
#Autowired
private Logger logger;
public void a() throws UserDefinedException {
try {
b();
} catch (Throwable ex) {
logger.log(ex);
throw new UserDefinedException(ex);
}
}
public String b() {
throw new NullPointerException("Transaction Logger");
}
}
Now outer transaction will be rolled back but inner new transaction will write data to DB.
Note rollbackFor attribute in #Transactional annotation for TestService class. This is necessary because by default Spring will not rollback transaction for chacked exceptions. This described behaviour that you got. In your case outer transaction was rollbacked only for runtime exceptions. That's why when you try to log NullPointerException the whole transaction is rolled back and your log record isn't added to the DB. But when you try to log UserDefinedException transaction is successfully commited despite the fact that error have been thrown and your log record is written to the DB.
Related
I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}
I currently have a spring application with hibernate and a PlataformTransactionManager running on Jboss/wildfly.
Some of the methods that manipulate the database also call a bean which contains a LinkedBlockingQueue. This queue stores logging messages that are periodically dispatched to someplace else on another thread (using simple spring #Scheduler).
Would it be possible to make my queue (inside a bean) transactional? ie. if the transaction rollback would I be able to "undo" any operations made on my Collection? What's the best strategy to implement this ?
So, in short something like:
#Service
#Transactional
public PersonService {
#Autowired
EntityManager EM;
#Autowired
LoggingBuffer logger;
public void addPerson(String name) {
EM.persist(new Person(.....));
logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
}
#Component
public class LoggingBuffer {
private Queue<String> q= new LinkedBlockingQueue<String>();
public add(String msg){
q.add(msg);
}
}
Try something like this
#Transactional
public void addPerson(String name) {
EM.persist(new Person(.....));
//logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
public void wrapAddPerson(String name){
List<String> localBuffer = new ArrayList<>();
try{
addPerson(name);
localBuffer.add(".....");
}catch(Exception e)
{
localBuffer.clear();
}
finally{
localBuffer.forEach(logger::add);
}
}
I am working on a gigaspace xap application which uses spring under the hood. The jini transaction manager provided by gigaspaces does not support serializable.
I have a class which uses spring-batch to process a file. Below is how it is invoking the job
public class FileProcessor implements BasicFileProcessor {
#Value("${feeddownload.basedir}")
private String baseDir;
#Autowired
private JobLauncher jobLauncher;
#Autowired
private Job cmJob;
#Autowired
private MapJobRepositoryFactoryBean repositoryFactoryBean;
#Override
public void process(RiskRunCompletion riskRunCompletion, VersionedSliceName versionedSliceName, GigaSpace gigaSpace) {
Transaction tx = gigaSpace.getCurrentTransaction();
try {
//Adding current time to the parameter, to enable multiple times calling job with same parameters
long currentTimeInMillis = System.currentTimeMillis();
JobParameter currentTimeInMillinsParam = new JobParameter(currentTimeInMillis);
Map parameterMap = new LinkedHashMap();
addDirectoryParams(valuationSliceRun, parameterMap);
parameterMap.put(CURRENT_TIME, currentTimeInMillinsParam);
JobParameters paramMap = new JobParameters(parameterMap);
JobExecution cmExecution = launchJobWithParameters(paramMap);
for (Throwable t : cmExecution.getAllFailureExceptions()) {
throw new RuntimeException(t);
}
} catch (Exception e) {
throw new RuntimeException("Exception during batch job", e);
} finally {
repositoryFactoryBean.clear();
}
}
private JobExecution launchJobWithParameters(JobParameters paramMap) throws Exception {
return jobLauncher.run(cmJob, paramMap);
}
}
The process method is invoked from a different class as below
public class FileBasedProcessingEventListener implements ApplicationContextAware {
#Value("${feeddownload.basedir}")
private String baseDir;
#Autowired
private BasicFileProcessor cmProcessor;
#Autowired
private FileBasedProcessingExceptionHandler fileBasedProcessingExceptionHandler;
public void handle(FileBasedProcessingEvent fileBasedProcessingEvent, GigaSpace gigaSpace) throws IOException {
LOGGER.info("Processing file based processing event : " + fileBasedProcessingEvent);
createLockFiles(fileBasedProcessingEvent);
handleEvent(fileBasedProcessingEvent, gigaSpace);
}
private void handleEvent(FileBasedProcessingEvent fileBasedProcessingEvent, GigaSpace gigaSpace) {
Transaction tx = gigaSpace.getCurrentTransaction();
cmProcessor.process(fileBasedProcessingEvent.getRiskRunCompletion(), versionedSliceName, gigaSpace);
}
}
Handle method is called from the framework. Now i am not sure why i am getting the exception as below
Caused by: org.springframework.transaction.InvalidIsolationLevelException: Jini Transaction Manager does not support serializable isolation level
at org.openspaces.core.transaction.manager.AbstractJiniTransactionManager.applyIsolationLevel(AbstractJiniTransactionManager.java:271)
at org.openspaces.core.transaction.manager.AbstractJiniTransactionManager.doJiniBegin(AbstractJiniTransactionManager.java:251)
at org.openspaces.core.transaction.manager.AbstractJiniTransactionManager.doBegin(AbstractJiniTransactionManager.java:207)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:372)
at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:417)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:255)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
None of the classes are marked as transactional, i am not sure why TransactionInterceptor is being invoked when i have not marked any class or any method as transaction it should not be of any concern. I also used Transaction tx = gigaSpace.getCurrentTransaction(); to check that transaction is not active it comes as null only
i am confused when none of the classes are marked as transactional why is spring trying to invoke this method under transaction
Looks like Gigaspaces transaction manager is based upon Spring transaction management infrastructure as can be inferred from the documentation here - so if you are using Gigaspaces transaction API then you are using Spring transaction management. Also worth looking is any transaction manager configuration inside xml configuration files which would point the exact transaction manager class.
Its indeed seems that transactions are in use, check if you define a transaction manger in your pu.xml, in particular check jobLauncher initialization.
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;
}
}
I have a Spring 3.1 MVC + Hibernate 3.6 project with its junit4 test suit. My problem is that there is no transaction starting in my test cases, even thought I added a #Transactional.
My test case calls a controller and a dao. In the controller, a transaction is started anyway, so it does not complain. In the dao, I added a #Transactional(propagation = Propagation.MANDATORY) to be sure it will take the test's transaction. And currently it raises an IllegalTransactionStateException, which I guess it means there is no current transaction.
I tried to create programmaticaly an transaction and it does work, which means the AOP proxy to get the dao service is not the cause of the problem. However I want to create a transaction with the #Transactional annotation.
here's my dao:
// ...imports...
#Repository("taskDao")
#Transactional(propagation = Propagation.MANDATORY)
public class TaskHome implements TaskDao {
private static final Log log = LogFactory.getLog(TaskHome.class);
private SessionFactory sessionFactory;
#Autowired
public TaskHome(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public Task findById(int id) {
log.debug("getting Task instance with id: " + id);
try {
Task instance = (Task) this.sessionFactory.getCurrentSession().get(
Task.class, id); // exception raised here!
if (instance == null) {
log.debug("get successful, no instance found");
} else {
log.debug("get successful, instance found");
}
return instance;
} catch (RuntimeException re) {
log.error("get failed", re);
throw re;
}
}
...
}
Here's my test case:
// ...imports...
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
private static ClassPathXmlApplicationContext context;
private static TaskDao taskDao;
#BeforeClass
public static void initHibernate() throws Exception {
context = new ClassPathXmlApplicationContext("applicationContext.xml");
taskDao = context.getBean("taskDao", TaskDao.class);
}
#Test
public void testOnSubmit() {
// expects an existing default transaction here
Task task = taskDao.findById(1); // fails already here
// ... calls the controller and does some tests.
}
}
I searched in all Spring's documentation and googled it in any way I could imagine, but I don't see why the transaction is not started.
Any help is very welcome.
When using #RunWith(SpringJUnit4ClassRunner.class) you should obtain beans from the application context created by SpringJUnit4ClassRunner rather than from your own one.
In your case things go wrong because #Transactional on the unit test creates a transaction in the application context managed by SpringJUnit4ClassRunner, but you call methods on the beans obtained from the application context created manually.
So, remove your #BeforeClass method and obtain TaskDao via autowiring:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "/test-config.xml", "/applicationContext.xml" })
#TransactionConfiguration(defaultRollback = true)
#Transactional
public class TestTaskController {
#Autowired
private TaskDao taskDao;
...
}
See also:
9.3.5 Spring TestContext Framework