When repository.save(t) is called from my service, which is in turn called from my controller, all works just fine, and the object is inserted into the database table; But, when the service is called from my test class, Hibernate returns the created object but does not really flush the transaction into the database. I have tried using #Transactinal and #Commit in my test class and also on my #Test methods, but no difference in the result. I have also tried other solutions which involve using org.springframework.test.context.transaction.TestTransaction class, but any method call on this class throws an exception.
this is my super class for test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public abstract class QaApplicationTest {
protected abstract void initializeTest() throws Exception;
protected abstract void cleanupTestEffects() throws Exception;
}
And this is my concrete test class:
public class RequestControllerTest extends QaApplicationTest {
#Autowired
private SiteService siteService;
#Autowired
private RequestService requestService;
#Test
#Transactional
public void givenObject_whenInsertToDB_thenCreated() throws Exception{
Site siteObject = siteService.save(siteObject); //Here I need a commit.
Request request = new Request(site.getId());
Request savedRequest = requestService.save(request); //Here database returns "Parent Key Not Found" error.
Assertions.assertTrue(savedRequest.getId()>0);
}
}
I know the #Transactional on test methods are used to roll back all the changes made inside the method, however, In my case, the changes are not even committed in the first place. And I have used #org.springframework.transaction.annotation.Transactional which is the correct annotation. I don't know which part I am doing wrong! Any idea?
My colleague found the issue; we had used a third-party library (Camunda) that had enabled batch-insert on Hibernate. So by disabling the batch operation the issue was resolved and the insert is actually taking place now. Not sure, why we faced this only in Spring Test and not in the main application though. if anyone has a comment, we appreciate it.
Related
I have below maven dependency & configuration set up
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
#Configuration
#EnableMongoAuditing
public class MongoConfig {
#Bean
MongoTransactionManager transactionManager(MongoDbFactory mongoDbFactory) {
return new MongoTransactionManager(mongoDbFactory);
}
}
Updated: I've taken the suggested solution to create a bean with #Transactional, and have it injected into my test class. Below is the service bean I created:
#Service
#Transactional
#RequiredArgsConstructor
public class MongoTransactionService {
private final UserRepo userRepo;
public void boundToFail() throws RuntimeException {
userRepo.save(User.builder().id("1").build());
throw new RuntimeException();
}
}
and test class where I inject a bean of MongoTransactionService:
#DataMongoTest(excludeAutoConfiguration = EmbeddedMongoAutoConfiguration.class,
includeFilters = #ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MongoTransactionService.class))
#ExtendWith(SpringExtension.class)
class MongoTransactionServiceTest {
#Autowired
UserRepo userRepo;
#Autowired
MongoTransactionService mongoTransactionService;
#Test
void testTransactional() {
try {
mongoTransactionService.boundToFail();
} catch (Exception e) {
// do something
}
val user = userRepo.findById("1").orElse(null);
assertThat(user).isNull();
}
}
I am expecting a call to boundToFail(), which throws a RuntimeException, would roll back the saved user, but the user still gets persisted in the database after the call.
It turns out that #DataMongoTest doesn't activate the auto-configuration for MongoDB transactions. I've filed a ticket with Spring Boot to fix that. In the mean time, you can get this to work by adding
#ImportAutoConfiguration(TransactionAutoConfiguration.class)
to your test class.
Note that using MongoDB transactions requires a replica set database setup. If that's not given the creation of a transaction will fail and your test case will capture that exception and the test will still succeed. The data will not be inserted but that's not due to the RuntimeException being thrown but the transaction not being started in the first place.
The question previously presented a slightly different code arrangement that suffered from other problems. For reference, here's the previous answer:
#Transactional needs to live on public methods of a separate Spring bean as the transactional logic is implemented by wrapping the target object with a proxy that contains an interceptor interacting with the transaction infrastructure.
You example suffers from two problems:
The test itself is not a Spring bean. I.e. there's no transactional behavior added to boundToFail(…). #Transactional can be used on JUnit test methods but that's controlling the transactional behavior of the test. Most prominently, to roll back the transaction to make sure changes to the data store made in the test do not affect other tests. See this section of the reference documentation.
Even if there was transactional logic applied to boundToFail(…), a local method call to the method would never trigger it as it doesn't pass the proxy that's applying it. See more on that in the reference documentation.
The solution to your problem is to create a separate Spring bean that carries the #Transactional annotation, get that injected into your test case and call the method from the test.
It feels so simple:
I have a ViewScoped bean (JPA2 + EE6 + Seam3, if that matters) where the user of the web application can invoke a method like this:
public void save() {
doEntityManagerStuff(); // manipulates data in the database
callRemoteWebservice(); // which is to read said data and propagate it to other systems
}
Unfortunately, save() starts a transaction at the opening curly bracket and doesn't commit it before the closing bracket, meaning that the new data is not available to the remote web service to read.
I have tried to explicitly extract and annotate the database work:
#TransactionAttribute(REQUIRES_NEW)
private void doEntityManagerStuff() {
blabla(); // database stuff
}
But that didn't have any impact at all. (Maybe because that's EJB stuff and I'm running on seam...?)
The only thing that worked for me so far was to inject #UserTransaction and force commit the transaction at the end of either save() or doEntityManagerStuff() but that felt incredibly dirty and dangerous.
The other alternative would be to turn off container-managed transactions for the entire project, but that means I'd have to make all my beans manage their transactions manually, just so I can make this one case work.
Is there a better way?
To answer my own question:
I only went half-way, and that's why it didn't work. I didn't know enough about EJBs and their boudaries, and naively though just annotating the doEntityManagerStuff(...) method with a transaction attribute in my view-scoped CDI/Seam bean would be enough.
It isn't.
When I moved said method into a separate, stateless EJB, injected that into my CDI/Seam bean and called it from there, everything worked as expected.
#Stateless
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class MyPersister {
...
public void doEntityManagerStuff() {
blabla(); // database stuff
}
...
}
and
#ViewScoped
public class MyWebsiteBean {
...
#Inject MyPersister persister;
...
public void save() {
persister.doEntityManagerStuff(); //uses its own transaction
callRemoteWebService();
}
...
}
I have a java EE project using JPA (transaction-type="JTA"), hibernate as provider. I write my beans to handle the CRUD things. The program running in JBOSS 7 AS.
I have an EntityManagerDAO :
#Stateful
public class EntityManagerDao implements Serializable {
#PersistenceContext(unitName = "dtdJpa")
private EntityManager entityManager;
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public Object updateObject(Object object) {
object = entityManager.merge(object);
return object;
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void createObject(Object object) {
entityManager.persist(object);
}
public void refresh(Object object) {
entityManager.refresh(object);
}
public <T> T find(Class<T> clazz, Long id) {
return entityManager.find(clazz, id);
}
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void deleteObject(Object object) {
entityManager.remove(object);
}
}
but when I invoke deleteObject, this exception comes out.
java.lang.IllegalArgumentException: Removing a detached instance com.test.User#5
How is this caused and how can I solve it?
EntityManager#remove() works only on entities which are managed in the current transaction/context. In your case, you're retrieving the entity in an earlier transaction, storing it in the HTTP session and then attempting to remove it in a different transaction/context. This just won't work.
You need to check if the entity is managed by EntityManager#contains() and if not, then make it managed it EntityManager#merge().
Basically, the delete() method of your business service class should look like this:
em.remove(em.contains(entity) ? entity : em.merge(entity));
In my case, I got the same error, when I tried to delete an object
using,
session.delete(obj)
without creating any transaction before that.
And the problem is solved by creating the transaction first(session.beginTransaction() and then deleting the object.
I hope my answer will help someone :)
Sometimes its simply because you are missing the #Transaction annotation for add, remove, update operations.
I faced the same problem. The detached entity should be re-attached. As #BalusC mentioned, using EntityManager.merge() should be used to attach the detached entity. EntityManager.merge() generates SQL Query which fetches the current state of the entity, on which EntityManager.remove() has to be performed. But in my case it didn't worked.
Try EntityManager.remove(EntityManager.find(Class<T>,arg)) instead. It worked for me.
In my experience, if I query an object from the DB then closed the entity manager then do a DB delete, the problem happens. Or if I copy that loaded object to another instance then do a delete, this problem also happens.
In my opinion there are 2 things to keep note:
The object must be in the same session that was created by the Entity Manager
And the object mustn't be transferred to another object while the Entity Manager's session is still opened.
Cheers
I have an #Embeddable class that uses property access to wrap another object that's not directly mappable by JPA via field access. It looks like this:
#Embeddable
#Access(AccessType.PROPERTY)
public class MyWrapper {
#NotNull
#Transient
private WrappedType wrappedField;
protected MyWrapper() {
}
public MyWrapper(WrappedType wrappedField) {
this.wrappedField = wrappedField;
}
#Transient
public WrappedType getWrappedField() {
return wrappedField;
}
public void setWrappedField(WrappedType wrappedField) {
this.wrappedField = wrappedField;
}
#Column(name = "wrappedTypeColumn")
protected String getJPARepresentation() {
return wrappedField.toString();
}
protected void setJPARepresentation(String jpaRepresentation) {
wrappedField = new WrappedType(jpaRepresentation);
}
}
Persisting an #Entity with a MyWrapper field works fine. But when I execute a query to load the Entity from the database, I get a NullPointerException. The stacktrace and some debugging shows that Eclipselink creates a new instance of MyWrapper by calling its default constructor and then calls the setJPARepresentation() method (as expected).
But now the unexpected happens: the stacktrace shows that the getJPARepresentation() is called from inside the setter, which then of course leads to a NullPointerException when return wrappedField.toString() is executed.
java.lang.NullPointerException
at MyWrapper.getJPARepresentation(MyWrapper.java:27)
at MyWrapper.setJPARepresentation(MyWrapper.java)
... 109 more
Fact is, there is obviously no call to the getter in the code and the stacktrace shows no line number indicating from where in the setter called the getter. So my conclusion would be, that the bytecode weaver of Eclipselink generated the call to the getter.
It's easy to build a workaround, but my question is: Why does Eclipselink do that?
P.S: I'm using EclipseLink 2.3.2.v20111125-r10461 in a GlassFish Server Open Source Edition 3.1.2 (build 23)
When weaving is enabled (default on Glassfish), EclipseLink will weave code into property get/set methods for,
change tracking
fetch groups (partial objects)
lazy (relationships)
For change tracking support the set method will be weaved to check if the new value is different than the old value, so it must call the get method to get the old value.
Now this is still odd, as since your are building a new object, I would not expect the change listener to be set yet, so would expect the change tracking check to be bypassed. You could decompile the code to see exactly what was generated.
The easiest fix is to just put in a null check in your get method, which is probably best in general for your code. You could also switch to field access, which will not have issues with side-affects in get/set methods. You could also use a Converter to handle the conversion, instead of doing the conversion in get/set methods.
I have an app managed by maven with two modules: one for persistence, and another for the webapp itself (gwt).
My tests in persistence module works like a charm, but, in webapp, when I execute the same method multiple times I got a java.lang.IllegalStateException: Attempting to execute an operation on a closed EntityManager..
I use guice-persist to inject the entity manager into my DAOs, and all my DAO methods have the #Transactional annotation.
In my webapp, I put a:
public class ScuvServletModule extends ServletModule {
#Override
protected void configureServlets() {
super.configureServlets();
install(MyPersistenceAPI.getModule()); // return my module and install it
filter("/*").through(PersistFilter.class);
/// another bindings...
}
}
If I remove the PersistFilter, it wotks, but randomly throws a Transaction Closed exception or something like that.
Any help?
I found the problem. It is the PersistFilter. Aparently, its a Singleton, my DAOs are singletons too, but the EntityManager isnt.
So, now I inject a Provider<EntityManager> instead EntityManager, and it works just like a charm.