I have an interface as follows:
public interface PackageRepository extends JpaRepository<DocPackage, String> {
}
Now I'm able to use this without any problem from my REST services by using:
#Resource
PackageRepository repo;
I can make transactional calls on it with no problem.
However when I try to load this from a worker thread:
public class MyWorker implements Runnable {
#Resource
PackageRepository repo;
#Transactional
private void doSomething() {
DocPackage pck = repo.findOne("key");
pck.setStatus(2);
repo.saveAndFlush(pck);
}
public void run() {
//stuff
doSomething();
//other stuff
}
}
new Thread(new MyWorker()).start();
I'm able to do reads on this repo just fine, but whenever I save and flush I get the exception:
javax.persistence.TransactionRequiredException: no transaction is in progress
Is there any way to get this done correctly?
Spring, by default, use proxies. This mean #Transaction works only when method called from outside of class.
To fix it extract you method to service. Inject your service in MyWorker and call it.
I think you need to add the #Transactional annotation to your method
Related
I have two classes
public class MyTest {
#Autowired
private MyService myService;
private void test() {
myService.writeToDb();
}
}
#Service
public class MyService {
#Transactional
public void writeToDb() {
// do db related stuff
}
}
I want to know if calling a method test() (which is a private method) from MyTest class would create a transaction.
P.S
I'm using Spring Boot. And Java 17.
It will work, whether you call the method of another object from a public or private method inside yours is an implementation detail. From callee's point of view, it's the same, it is not even aware of the caller's context.
Spring AOP uses the Proxy pattern to handle those scenarios. It means you are not directly receiving a MyService bean, but a MyServiceSpringCreatedProxy (not the actual name, check in debug mode and you'll see), which is actually handling transactions around methods.
So as long as the call passes through the Spring's proxy, the #Transactional will be accounted for as expected. Bear in mind that it doesn't mean a new transaction is open, it depends if another already exists and your configuration.
However, any self call (to a public or a private method) would not pass through the proxy and then #Transactional would not be working.
#Service
public class MyService {
// can be private, public or whatever
public void callRelatedStuff() {
//self call, no transactional work done
writeToDb();
}
#Transactional
public void writeToDb() {
// do db related stuff
}
}
I'm having a problem when I trying to delete data from the DB using multiple threads with Hibernate.
Repo:
#Modifying
#Query("DELETE FROM Customer cus WHERE cus.customerId in :customerIds")
public void deleteByCustomerIds(#Param("customerIds") List<Long> customerIds);
Service:
public runDelete (List<Long> customerIds) {
List<List<Long>> partitions = Lists.partition(customerIds, 5000);
for(int i = 0; i < partitions.size(); i++ ) {
final int index = i;
Runnable thread = () -> deleteCustomersInBatches(partitions.get(index));
new Thread(thread).start();
}
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
private void deleteCustomerInBatches(List<Long> customerIds) {
for (List<Long> batch : Lists.partition(oldCalcIds, 1000)) {
customerRepo.deleteByCustomerIds(batch);
}
}
This is how code looks like, I have the #Transactional tag on the service layer where the repo call is being made.
at java.lang.Thread.run(Thread.java:748) Caused by:
javax.persistence.TransactionRequiredException: Executing an
update/delete query
at org.hibernate.jpa.spi.AbstractQueryImpl.executeUpdate(AbstractQueryImpl.java:54)
I keep getting this error. Any help is appreciated.
It's because you're invoking #Transactional method from within same bean.
#Transactional only works on methods invoked on proxies created by spring. It means, that when you create a #Service or other bean, method called from the outside will be transactional. If invoked from within bean, nothing will happen, as it doesn't pass through proxy object.
The easiest solution would be to move the method to another bean. If you really want to keep it within same component, then you need to invoke it, so that it gets wrapped in proxy by spring AOP. You can do this like that:
private YourClass self;
#Autowired
private ApplicationContext applicationContext;
#PostConstruct
public void postContruct(){
self = applicationContext.getBean(YourClass.class);
}
Then invoking method on self would result in opening a transaction.
I have some manager, where I have userDao, which is set by
#Autowired
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
I want start new thread in manager and pass this dao in constructor.
Like this:
new MyThread(userDao).start();
It is safe? Or possible? If no, how can I do this?
Thank you for your replye.
Create a function that accepts your DAO as a parameter. Annotate the function with #Aysnc
When calling the function keep in mind the following:
When you call an Async annotated function from within the same class the call will be synchronous (this is to do with proxied objects)
Async methods will not participate in the same transaction context as the caller
This approach encourages you to think about your class structure and is to be encouraged. For instance you might want to also wrap it with #Retryable in the case of failure.
Of course, you also do something like
ExecutorService executorService = Executors.newFixedThreadPool(3);
MyWorker worker = new MyWorker(UserDao userDao);
executorService.submit(worker);
private class MyWorker implements Callable {
private UserDao userDao
MyWorker(UserDao userDao){
this.userDao = userDao;
}
public void call() {
.. do something
}
}
But that's a lot more code.
I have a singleton EJB whose business methods are all #Lock(READ). However, on special ocassions, some of them call a private method that persists stuff on a database. What's the best way to handle this situation? Should I:
Use #Lock(WRITE) for that private method even though it's not a business method? If so, is this a reliable behaviour?
Do the synchronization on the private method myself? If so, is it safe to synchronize over the EntityManager?
Do something completely different?
This only partially answers your question, but here goes.
You could put the private methods in a "private" businness interface and call them via the container like this:
TestEJB.java:
#Stateless
public class TestEJB implements PrivateIface, PublicIface {
SessionContext ctx;
public void doBusiness() {
PrivateIface businessObject = ctx.getBusinessObject(PrivateIface.class);
businessObject.doPrivateBusinness();
}
#SomeContainerAnnotation
public void doPrivateBusinness() {
}
}
#Local
interface PrivateIface {
void doPrivateBusinness();
}
PublicIface.java:
#Local
public interface PublicIface {
void doBusiness();
}
I'm working on a recovery monitor which waits for 5 minutes and fires an alert if system has not been recovered yet. The monitor needs to be started at start up and to fire alert only once. The source code looks like this:
#Stateless
public class RecoveryMonitor {
#Inject TimerService timerService;
#Inject MyAlertService alertService;
#Inject SystemRecovery systemRecovery;
public void scheduleMonitor() {
timerService.createSingleActionTimer(TimeUnit.MINUTES.toMillis(5),
new TimerConfig);
}
#Timeout
public void timeout() {
if (!systemRecovery.isDone) {
alertService.alert("System recovery failed");
}
}
}
So, the problem here is how to schedule a task, i.e. invoke scheduleMonitor method. I cannot use #PostConstruct as it's not allowed to. I think about using #Schedule, but it executes a method periodically while I only to do it once. Any solutions and/or suggestions are welcome. Thanks.
L
UPDATE: by making the class not Stateless anymore, e.g. make it a #Singleton, I am able to start scheduling using #PostConstruct. This is not a complete solution but it works for me:
#Singleton
public class RecoveryMonitor {
#Inject TimerService timerService;
#Inject MyAlertService alertService;
#Inject SystemRecovery systemRecovery;
#PostConstruct
public void scheduleMonitor() {
timerService.createSingleActionTimer(TimeUnit.MINUTES.toMillis(5),
new TimerConfig);
}
#Timeout
public void timeout() {
if (!systemRecovery.isDone) {
alertService.alert("System recovery failed");
}
}
}
If you have a Servlet Environment you could fire a CDI Event(e.g. ApplicationStartedEvent) within a ServletContextListener and observe that event in your EJB. This kind of startup logic has to be done manually in CDI 1.0. Future versions will probably contain something similar.
If you have questions on how to do that, just ask :)