Can I prevent a LINQ DataContext from using a transaction when a TransactionScope is present? - linq

I'm using a TransactionScope in a model controller class that coordinates several lower level, data access classes. The data access classes each use their own LINQ DataContext, and thanks to the magic of TransactionScope, they all participate in the same transaction if one is present.
Under normal circumstances, this is perfect and everything works. However, I've added an activity logging class and one of the places it can write to is the database. Unfortunately, it automatically picks up on the TransactionScope and if the transaction gets rolled back, so do all the log entries.
I've checked the Transaction property of the DataContext and it's null, as expected, so I'm not sure how to tell it to ignore the TransactionScope.

In your logging class, wrap your using(new datacontext()) into:
using (var s = new TransactionScope(TransactionScopeOption.Suppress)) {
}

Related

Spring Data problem - derived delete doesn't work

I have a spring boot application (based off spring-boot-starter-data-jpa. I have an absolute minimum of configuration going on, and only a single table and entity.
I'm using CrudRepository<Long, MyEntity> with a couple of findBy methods which all work. And I have a derived deleteBy method - which doesn't work. The signature is simply:
public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
Long deleteBySystemId(String systemId);
// findBy methods left out
}
The entity is simple, too:
#Entity #Table(name="MyEntityTable")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="MyEntityPID")
private Long MyEntityPID;
#Column(name="SystemId")
private String systemId;
#Column(name="PersonIdentifier")
private String personIdentifier;
// Getters and setters here, also hashCode & equals.
}
The reason the deleteBy method isn't working is because it seems to only issue a "select" statement to the database, which selects all the MyEntity rows which has a SystemId with the value I specify. Using my mysql global log I have captured the actual, physical sql and issued it manually on the database, and verified that it returns a large number of rows.
So Spring, or rather Hibernate, is trying to select the rows it has to delete, but it never actually issues a DELETE FROM statement.
According to a note on Baeldung this select statement is normal, in the sense that Hibernate will first select all rows that it intends to delete, then issue delete statements for each of them.
Does anyone know why this derived deleteBy method would not be working? I have #TransactionManagementEnabled on my #Configuration, and the method calling is #Transactional. The mysql log shows that spring sets autocommit=0 so it seems like transactions are properly enabled.
I have worked around this issue by manually annotating the derived delete method this way:
public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
#Modifying
#Query("DELETE FROM MyEntity m where m.systemId=:systemId")
Long deleteBySystemId(#Param("systemId") String systemId);
// findBy methods left out
}
This works. Including transactions. But this just shouldn't have to be, I shouldn't need to add that Query annotation.
Here is a person who has the exact same problem as I do. However the Spring developers were quick to wash their hands and write it off as a Hibernate problem so no solution or explanation to be found there.
Oh, for reference I'm using Spring Boot 2.2.9.
tl;dr
It's all in the reference documentation. That's the way JPA works. (Me rubbing hands washing.)
Details
The two methods do two different things: Long deleteBySystemId(String systemId); loads the entity by the given constraints and ends up issuing EntityManager.delete(…) which the persistence provider is about to delay until transaction commits. I.e. code following that call is not guaranteed that the changes have already been synced to the database. That in turn is due to JPA allowing its implementations to actually do just that. Unfortunately that's nothing Spring Data can fix on top of that. (More rubbing, more washing, plus a bit of soap.)
The reference documentation justifies that behavior with the need for the EntityManager (again a JPA abstraction, not something Spring Data has anything to do with) to trigger lifecycle events like #PreDelete etc. which users expect to fire.
The second method declaring a modifying query manually is declaring a query to be executed in the database, which means that entity lifecycles do not fire as the entities do not get materialized upfront.
However the Spring developers were quick to wash their hands and write it off as a Hibernate problem so no solution or explanation to be found there.
There's detailed explanation why it works the way it works in the comments to the ticket. There are solutions provided even. Workarounds and suggestions to bring this up with the part of the stack that has control over this behavior. (Shuts faucet, reaches for a towel.)

How can I test that a JPA save actually saves data?

I am using JPA with Spring and saving an entity in a test. In the process of writing a test to validate that an entity's relationship with another entity is correctly set up, I have come across a problem that I come across frequently. I have a test method (set to rollback) that:
Creates entity
Saves entity
Flushes
Retrieves entity
Validates entity
The problem is that when I look at the Hibernate logs, I only see a single insert to the database where I'd expect to see an insert and then a select.
I know this is because Hibernate's trying to save me some time and knows that it's got the entity with the ID I'm trying to retrieve but that bypasses an important step: I want to make sure that the entity actually made it to the database and looks like what I thought it should. What's the best way to deal with this so I can test that the entity is actually in the database?
Note: I assume this involves somehow detaching the entity or telling Hibernate to clear its cache but I'm not sure how to do that when all I have access to is a JpaRepository object.
Some code:
public interface UserRepository extends JpaRepository<User, Long> {
//...
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = JpaConfig.class, // JpaConfig just loads our config stuff
loader = AnnotationConfigContextLoader.class)
#TransactionConfiguration(defaultRollback = true)
public class UserRepositoryTest {
#Test
#Transactional
public void testRoles() {
User user = new User("name", "email#email.com");
// eventually more here to test entity-to-entity relationship
User savedUser = userRepository.save(user);
userRepository.flush();
savedUser = userRepository.findOne(savedUser.getId());
Assert.assertNotNull(savedUser);
// more validation here
}
}
You basically want to test Hibernate's functionality instead of your own code. My first suggestion: don't do it! It is already tested and validated many times.
If you really want to test it, there are a couple of options:
Execute a query (rather than a get. The query will get executed (you should see it in the log) and the result interpreted. The object you get back would still be the same object you saved, since that is in the session.
You can evict the object from the session and then get it again. If you use SessionFactory.getCurrentSession(), you'll get the same season that the repository is using. With that you can evict the object.
You have two strategies:
issue a native SQL query therefor bypassing any JPA cache.
ensure the persistence context is cleared before reloading.
For (1) you can change your tests to extend the following Spring class which, in addition to automatically beginning/rolling back a transaction at the start/end of each test, will give you access to a Spring JdbcTemplate you can use to issue the native SQL.
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/test/context/junit4/AbstractTransactionalJUnit4SpringContextTests.html
http://docs.spring.io/spring-framework/docs/2.5.6/api/org/springframework/jdbc/core/simple/SimpleJdbcTemplate.html
For (2) you can clear the persistence context by doing the following (where the EntityManagerFactory is injected into your test:
EntityManagerFactoryUtils.getTransactionalEntityManager(entityManagerFactory).clear();
See the following base test class which I normally use and demonstrates the above and also allows for populating the database with known data before each test (via DBUnit).
https://github.com/alanhay/spring-data-jpa-bootstrap/blob/master/src/test/java/uk/co/certait/spring/data/repository/AbstractBaseDatabaseTest.java
(In fact in the above I am actually creating a new JdbcTemplate by injecting a datasource. Can't remember why...)

Spring #Transactional annotation behaves weird

I have the following method:
#Transactional
public void onEmailMessage() {
this.departmentService.removeUserFromDepartments(user, depsids);
this.departmentService.getDepartmentUsers(user.id);
}
The weird thing when i invoke this method, the first line:
this.departmentService.removeUserFromDepartments(user, depsids);
is called but the DB is not changing at all and the user is still connected to the deparment (many to many relation)
afterwards the method :
this.departmentService.getDepartmentUsers(user.id);
is called and returns users that are connected to the department including the removed user from line#1.
when the method returns - if i check the DB the user i removed is actually been removed from the table!
can i make the query return the actual updated values??
There is nothing weird about this. You are performing two different queries within the same transaction. Persistence context is updated, but the transaction hasn't been committed yet, and you can't see your changes after first line is finished. Transaction is a set of statements (in this case - statements created by those two methods of yours) which gets executed after commit is invoked. When the whole (onEmailMessage) method finished it's job, the transaction is committed and you are seeing the changes.
The solutions would be:
Make them as two separate transactions. For e.g:
#Transactional
public void removeUser(...) {
someInstance.departmentService.removeUserFromDepartments(user, depsids);
}
And:
#Transactional
public List<?> getUsers(...) {
return someInstance.departmentService.getDepartmentUsers(user.id);
}
Then the highest level would be onEmailMessage() method, which has to be non-transactional and in separate class then these two methods above. Call them both in this level and it will work.
You have marked it as Transactional. Changes in DB is made after executing all the queries. Either all of the operations will be committed or none.
The transaction hasn't been committed yet so changes won't necessarily have been written to the DB.
You could try calling
entityManager.flush();
after removeUserFromDepartments() but before getDepartmentUsers() to force the DB changes to be written before the commit.

Spring transaction propagation_required issue

In our java project we are using ORM with hibernate and spring.
I had problems in deleting persistent objects. For example this sample method gets entities by ids and then delete them:
#Transactional
public void remove(List<Long> ids) {
SearchTemplate template = new SearchTemplate();
template.addParameter("milestoneId",ids);
List <InvoiceQueue> items = this.findByCriteria(template);
...
this.delete(items);
}
Method executes Ok without any exception but doesn't actually delete the items from the DB.
Adding the following annotation to the method definition #Transactional(propagation = Propagation.REQUIRES_NEW) solves the problem.
Can anyone explain why it doesn't work with the default propagation type PROPAGATION_REQUIRED.
Thanks in advance.
Environment details :
hibernate.version 3.5.5-Final, spring.version 3.0.5.RELEASE
Really just repeating what #PeterBagyinszki said in his comment, but the reason quite probably is that the transaction within which your delete occurs gets rolled back due to some other part throwing an exception, and all the changes made during the transaction get canceled. With Propagation.REQUIRES_NEW, the delete is done within it's own separate nested transaction. The outcome of the nested transaction (committed or rolled back) won't affect the "outer" transaction and vice versa.
Check your logs to see what is causing the transaction to be rolled back, note that even something like a simple SELECT -query failing with something like NoResultException will cause the transaction to roll back, unless you explicitly state in the #Transactional-annotation it not to roll back on certain exceptions.

lazy loading in granite ds

how will i load my entities in my flex application using lazy loading .I have a deep object graph
GraniteDS, together with its data management framework, lets you transparently load your uninitiated associations: see documentation here. So, basically, you don't have to do anything special in order to initialize your lazy collections/proxies, you only need to access one of them on the client side (asking for the size of a collection for example) and it will trigger a call to the server and fetch the uninitialized data.
If you don't want or can't use transparent lazy-loading, you need to write a specific initialization method which must have access to an EntityManager, receive your entity as a parameter, initialize the required association and send the entity back to the client.
AFAIK it's impossible. You should have opened hibernate session to do this.
In my project I'm doing what you need this way:
I have spring service like this:
public interface SomeObjectManager {
List<SomeObject> getObjects(); // here we have lazy loading for SomeObject's properties
SomeObject getFullObject(long objectId); // here we're loading full object
}
Some properties of SomeObject use lazy loading. To load them I use HQL query like this:
SELECT s FROM SomeObject s
LEFT JOIN FETCH s.child children
LEFT JOIN FETCH children.items items
LEFT JOIN FETCH items.property
WHERE s.id=:id
This query forces hibernate to load properties defined lazy.
So if you don't need fully loaded SomeObject instances you use getObjects() method. If then you need details on concrete SomeObject instance you use getFullObject() method.
Hope this helps.

Resources