Spring #Transactional and Neo4j OGM session.getTransaction() - neo4j

What is the correlation between Spring org.springframework.transaction.annotation.Transactional annotation and Neo4j OGM org.neo4j.ogm.session.Session.getTransaction() method.
I'm trying to access the current transaction via session.getTransaction() inside of the method annotated with Spring #Transactional but always getting null.
I have added a following code inside of my Spring MVC RestController method:
Transaction tx = session.beginTransaction();
try {
for (int i = 0; i < 10; i++) {
initializeNode(node);
}
}
tx.commit();
} catch (Throwable th) {
logger.error("Error while inserting mock data", th);
th.printStackTrace();
} finally {
tx.close();
}
in case of the following method:
private void initializeNode(TestNode node) {
System.out.println(session.getTransaction());
}
it prints current tx - so far everything is okay.
But in case of the following method:
private void initializeNode(TestNode node) {
System.out.println(session.getTransaction());
User admin = userDao.findByUsername("admin");
}
first time it prints current tx and then null... transaction disappear before commit for a some reason..
this is findByUsername method:
#Service
#Transactional
public class UserDaoImpl implements UserDao {
#Override
#Transactional(readOnly = true)
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}
...
}
Right after that on commit I'm getting a following exception:
org.neo4j.ogm.exception.TransactionManagerException: Transaction is not current for this thread
at org.neo4j.ogm.session.transaction.DefaultTransactionManager.commit(DefaultTransactionManager.java:100)
at org.neo4j.ogm.transaction.AbstractTransaction.commit(AbstractTransaction.java:83)
at org.neo4j.ogm.drivers.embedded.transaction.EmbeddedTransaction.commit(EmbeddedTransaction.java:77)
What am I doing wrong ? Why transaction disappears ?

There are several issues and themes going on in this question. I will try and break them down and hopefully at the end it will all make sense.
As of the latest release of Spring Data Neo4j (4.1.x) there is no correlation between Spring's #Transactional and the Neo4j OGM's Session.getTransaction() or Session.beginTransaction() when called directly.
In your first two code blocks you are completely managing your OGM session lifecycle directly. Spring is not involved at all at this point and as you say it executes as expected.
In your updated third code block you are now expecting the session that you have manually opened to work with your Spring managed DAO. What will happen here is depends on the Neo4j Driver you are using with SDN but essentially because your DAO has the #Transactional annotation, Spring will intercept the call and start a brand new transaction all on its own on top of the one you are manually managing. At this point, we can't make any guarantees about the behaviour but the easiest explanation would be to say that it will be unexpected (again, depending on the driver used).
So how can you fix this?
I'm going to assume you want to use Spring Transactions and Spring Data Neo4j. If that's the case you will want to start by:
Changing your DAO to use Spring Data Repositories. This gives you a lot of free persistence functionality like finders, saves, deletes etc.
Putting the #Transactional annotation around the unit of work you want to accomplish. You might have a method that calls userRepository.findByUserName(), modifies that user and calls userRepository.save(user). In a web environment this is typically some sort of service method.
Removing any code that manually starts or ends an OGM session transaction.
You can find a very short code sample here and a longer code sample here.
A more comprehensive guide can also be found here.
In Spring Data Neo4j 4.2.x we hope to introduce some more powerful and friendlier #Transactional behaviour so keep posted for that update.

Related

How to correctly address "can't add a _parent field that points to an already existing type, that isn't already a parent" on app startup

I'm running into a problem with my Elasticsearch Document index creation failing on startup with "java.lang.IllegalArgumentException: can't add a _parent field that points to an already existing type, that isn't already a parent". I'm not sure if this is due to a version upgrade or b/c I am starting with a brand new Elasticsearch server install.
Contrived example that shows what I'm seeing:
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user")
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department")
public class DepartmentSearchResult implements Serializable {
...
}
When I start my application I get that exception. If I check the ElasticSearch server, I see the "hr_index" index and the "department" mapping, but the "user" mapping is not created.
If I understand the error, it's because "department" is being created and then when Spring tries to create "user" with "department" as its parent, it doesn't like that, since department wasn't previously marked as a parent when it was created.
Is there some way (via annotation?) to denote DepartmentSearchResult as being a parent when it's created somehow?
Or, is it possible to give a hint to Spring Data Elasticsearch as to what order it should create the indices/mappings? I have seen some other posts (Spring Data Elasticsearch Parent/Child Document Repositories / Test execution error) but disabling auto creation and then manually creating it myself (either as part of my Spring codebase or external to the app) seems kind of "un-Spring-y" to me?
Or, is there some other approach I should be taking?
(This is a working Spring application that had been using Spring 4.2.1 and Spring Data Release Train Gosling, that I'm attempting to upgrade to use Spring 5.0.0 and Spring Data Release Train Kay. As part of this I am starting with a fresh Elasticsearch install, and so I'm not sure if this error is coming from the upgrade or just b/c the install is clean).
In the SD ES, issues related to the parent-child relationship at now really poorly developed.
The problem is most likely due to the fact that you are using a clean installation of Elasticsearch. Before the update, the problem did not arise, because mappings have already been created. For the solution, you can use elasticsearchTemplate, which is part of SD ES, and ApplicationListener. It's simple. Just 3 steps.
Drop index in ES (it only needs one time):
curl -XDELETE [ES_IP]:9200/hr_index
Tell SD ES not to create indices and mappings automatically
// UserSearchResult.java
#Document(indexName = "hr_index", type = "user", createIndex = false)
public class UserSearchResult implements Serializable {
...
#Field(type=FieldType.keyword)
#Parent(type="department")
private String departmentCode;
...
}
// DepartmentSearchResult.java
#Document(indexName = "hr_index", type = "department", createIndex = false)
public class DepartmentSearchResult implements Serializable {
...
}
Add a ApplicationListener:
#Component
public class ApplicationStartupListener implements ApplicationListener<ContextRefreshedEvent> {
#Autowired
private ElasticsearchTemplate elasticsearchTemplate;
//Mapping for child must be created only if mapping for parents doesn't exist
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
elasticsearchTemplate.createIndex(DepartmentSearchResult.class);
try {
elasticsearchTemplate.getMapping(DepartmentSearchResult.class);
} catch (ElasticsearchException e) {
elasticsearchTemplate.putMapping(UserSearchResult.class);
elasticsearchTemplate.putMapping(DepartmentSearchResult.class);
}
}
}
P.S. Among other things, it is worth paying attention to the fact that with the release of ES 5.6, a process for removing types began. This inevitably entails the removal of the parent-child relationship. In one of the next releases of the SD ES, we will provide the opportunity to work with joins. Working with parent-child relationships is unlikely to be improved

How spring handle calling the customed method passed in the #PreAuthorize annotation

I am new to spring security, and recently worked on a project that require to do method level security.
I managed to handle it like below:
#Repository
public class EmployeeDaoImpl{
#PreAuthorize("#mySecurityService.canAdd('ROLE_ADMIN') ")
public void addEmployee(EmployeeEntity employee) {
try{
this.sessionFactory.getCurrentSession().save(employee);
}catch(AccessDeniedException e){
}
}
}
#Component
public class MySecurityService {
public boolean canAdd(String user) {
System.out.println("Entered has permission..........");
if(user.equals("ROLE_ADMIN")){
return false;
}
return false;
}
}
so far so good, everthing is working fine and smoothly.
My question here is about performance, how spring behind the scenes handle calling the method in the #PreAuthorize(), do spring do any kind of object/method caching or proxy-ing, or every time call the method by reflection, and how this will affect the performance?
I've did a lot of search and only found this link, it helped me, but do you have any further explaination specific to #PreAuthorize case.
http://spring.io/blog/2007/07/19/debunking-myths-proxies-impact-performance/
Hope my question is clear, Thanks.
First, the expression needs to be parsed, and only then it can be evaluated.
As a result of parsing, the expression is converted into a tree of SpelNodes. In particular, MethodReference is a SpelNode responsible for method calling.
The parsing part is cached well in PreInvocationAuthorizationAdvice.
The details of the implementation of MethodReferencecan be found here:
org.springframework.expression.spel.ast.MethodReference
org.springframework.expression.spel.ast.MethodReference#getValueInternal(...)
org.springframework.expression.spel.support.ReflectiveMethodExecutor
There is caching and the java.lang.reflect.Method reference is evaluated only once (if the target object remains the same type).
So this is pretty the most that could have been done by Spring. Further improvements require byte code generation, which would be an overkill in my opinion.

grails withTransaction - why is it on a domain object?

We need to be able to rollback a complex transaction in a service, without throwing an exception to the caller. My understanding is that the only way to achieve this is to use withTransaction.
The question is:
why do I have to call this on a domain object, such as Books.withTransaction
What if there is no relevant domain object, what is the consequence of picking a random one?
Below is more or less what I am trying to do. The use case is for withdrawing from an account and putting it onto a credit card. If the transfer fails, we want to rollback the transaction, but not the payment record log, which must be committed in a separate transaction (using RequiresNew). In any case, the service method must return a complex object, not an exception.
someService.groovy
Class SomeService {
#NotTransactional
SomeComplexObject someMethod() {
SomeDomainObject.withTransaction{ status ->
DomainObject ob1 = new DomainObject.save()
LogDomainObject ob2 = insertAndCommitLogInNewTransaction()
SomeComplexObject ob3 = someAction()
if (!ob3.worked) {
status.setRollbackOnly() // only rollback ob1, not ob2!
}
return ob3
}
}
}
The above is flawed - I assume "return ob3" wont return ob3 from the method, as its in a closure. Not sure how to communicate from inside a closure to outside it.
To your primary question: you can pick a random domain object if you want, it won't do any harm. Or, if you prefer, you can find the current session and open a transaction on that instead:
grailsApplication.sessionFactory.currentSession.withTransaction { /* Do the things */ }
Stylistically I don't have a preference here. Others might.
Not sure how to communicate from inside a closure to outside it.
In general this could be hard; withTransaction could in principle return anything it wants, no matter what its closure argument returns. But it turns out that withTransaction returns the value returned by its closure. Here, watch:
groovy> println(MyDomainObject.withTransaction { 2 + 2 })
4
By convention, all withFoo methods which take a closure should work this way, precisely so that you can do the thing you're trying to do.
I'm assuming this question was from a grails 2 application and this problem from 2015 has been fixed before now.
I can't find this in any of the grails 2 documentation, but services have a magic transactionStatus variable injected into their methods. (at least in grails 2.3.11)
You can just leave all the annotations off and use that injected variable.
Class SomeService {
SomeComplexObject someMethod() {
DomainObject ob1 = new DomainObject.save()
LogDomainObject ob2 = insertAndCommitLogInNewTransaction()
SomeComplexObject ob3 = someAction()
if (!ob3.worked) {
transactionStatus.setRollbackOnly() // transactionStatus is magically injected.
}
return ob3
}
}
This feature is in grails 2, but not documented. It is documented in grails 3.
https://docs.grails.org/latest/guide/services.html#declarativeTransactions
search for transactionStatus.

Spring Data Neo4j - Unit Test - Transaction rollbacked but data not deleted

I am building an application using spring-boot (1.1.8.RELEASE), spring-data-neo4j (3.2.0.RELEASE) in order to connect to a stand alone neo4j server via rest api. I am using spring-test in order to test the application I have implemented a unit test to create a Node and retrieved it. It is working well but the new node remained in the database after the test is completed, however I expect the transaction to be rollbacked and the node deleted
However in the console I can see the following statement.
"Rolled back transaction after test execution for test context...
** I don't understand why based on the console the roll back seems to have occured but the transaction has been committed to the database. **
It would be really appreciated if somebody could help me to figure out where the issue is coming from.
Find below my spring configuration
#Configuration
#ComponentScan
#EnableTransactionManagement
#EnableAutoConfiguration
public class AppConfig extends Neo4jConfiguration {
public AppConfig() {
setBasePackage("demo");
}
#Bean
public GraphDatabaseService graphDatabaseService(Environment environment) {
return new SpringRestGraphDatabase("http://localhost:7474/db/data");
}
}
Find below my test class
#SuppressWarnings("deprecation")
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = AppConfig.class)
#Transactional
public class AppTests {
#Autowired
private Neo4jTemplate template;
#Test
public void templateTest() {
Person person = new Person();
person.setName("Benoit");
person.setBorn(1986);
Person newPerson = template.save(person);
Person retrievedPerson = template.findOne(newPerson.getNodeId(),Person.class);
Assert.assertEquals("Benoit", retrievedPerson.getName());
}
}
I tried to add the following annotation in my unit test class but it did not change anything:
#TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
I also tried to add the following in my unit test based on what I have seen in other posts
implements ApplicationContextAware
Thank you for your help
Regards
The behavior you are experiencing is to be expected: there is nothing wrong with transaction support in the Spring TestContext Framework (TCF) in this regard.
The TCF manages transactions via the configured transactionManager.
So when you switched to an embedded database and configured the transaction manager with the data source for that embedded database, that works perfectly. The issue is that the transaction support in Neo4J-REST does not tie in with Spring's transaction management facilities. As Michael Hunger stated in the other thread you referenced, an upcoming version of the Neo4J-REST API should address this issue.
Note that annotating your test class with #TransactionConfiguration has zero effect since you are merely overriding the defaults with the defaults which achieves nothing. Furthermore, implementing ApplicationContextAware in a test class has no effect on transaction management.
Regards,
Sam (spring-test component lead)

How to force only one transaction within multiple DbContext classes?

Background:
From another question here at SO I have a Winforms solution (Finance) with many projects (fixed projects for the solution).
Now one of my customers asked me to "upgrade" the solution and add projects/modules that will come from another Winforms solution (HR).
I really don't want to keep these projects as fixed projects on the existing finance solution. For that I'm trying to create plugins that will load GUI, business logic and the data layer all using MEF.
Question:
I have a context (DbContext built to implment the Generic Repository Pattern) with a list of external contexts (loaded using MEF - these contexts represent the contexts from each plugin, also with the Generic Repository Pattern).
Let's say I have this:
public class MainContext : DbContext
{
public List<IPluginContext> ExternalContexts { get; set; }
// other stuff here
}
and
public class PluginContext_A : DbContext, IPluginContext
{ /* Items from this context */ }
public class PluginContext_B : DbContext, IPluginContext
{ /* Items from this context */ }
and within the MainContext class, already loaded, I have both external contexts (from plugins).
With that in mind, let's say I have a transaction that will impact both the MainContext and the PluginContext_B.
How to perform update/insert/delete on both contexts within one transaction (unity of work)?
Using the IUnityOfWork I can set the SaveChanges() for the last item but as far as I know I must have a single context for it to work as a single transaction.
There's a way using the MSDTC (TransactionScope) but this approach is terrible and I'd reather not use this at all (also because I need to enable MSDTC on clients and server and I've had crashes and leaks all the time).
Update:
Systems are using SQL 2008 R2. Never bellow.
If it's possible to use TransactionScope in a way that won't scale to MSDTC it's fine, but I've never achieved that. All the time I've used TransactionScope it goes into MSDTC. According to another post on SO, there are some cases where TS will not go into MSDTC: check here. But I'd really prefer to go into some other way instead of TransactionScope...
If you are using multiple contexts each using separate connection and you want to save data to those context in single transaction you must use TransactionScope with distributed transaction (MSDTC).
Your linked question is not that case because in that scenario first connection do not modify data so it can be closed prior to starting the connection where data are modified. In your case data are concurrently modified on multiple connection which requires two-phase commit and MSDTC.
You can try to solve it with sharing single connection among multiple contexts but that can be quite tricky. I'm not sure how reliable the following sample is but you can give it a try:
using (var connection = new SqlConnection(connnectionString))
{
var c1 = new Context(connection);
var c2 = new Context(connection);
c1.MyEntities.Add(new MyEntity() { Name = "A" });
c2.MyEntities.Add(new MyEntity() { Name = "B" });
connection.Open();
using (var scope = new TransactionScope())
{
// This is necessary because DbContext doesnt't contain necessary methods
ObjectContext obj1 = ((IObjectContextAdapter)c1).ObjectContext;
obj1.SaveChanges(SaveOptions.DetectChangesBeforeSave);
ObjectContext obj2 = ((IObjectContextAdapter)c2).ObjectContext;
obj2.SaveChanges(SaveOptions.DetectChangesBeforeSave);
scope.Complete();
// Only after successful commit of both save operations we can accept changes
// otherwise in rollback caused by second context the changes from the first
// context will be already accepted = lost
obj1.AcceptAllChanges();
obj2.AcceptAllChanges();
}
}
Context constructor is defined as:
public Context(DbConnection connection) : base(connection,false) { }
The sample itself worked for me but it has multiple problems:
First usage of contexts must be done with closed connection. That is the reason why I'm adding entities prior to opening the connection.
I rather open connection manually outside of the transaction but perhaps it is not needed.
Both save changes successfully run and Transaction.Current has empty distributed transaction Id so it should be still local.
The saving is much more complicated and you must use ObjectContext because DbContext doesn't have all necessary methods.
It doesn't have to work in every scenario. Even MSDN claims this:
Promotion of a transaction to a DTC may occur when a connection is
closed and reopened within a single transaction. Because the Entity
Framework opens and closes the connection automatically, you should
consider manually opening and closing the connection to avoid
transaction promotion.
The problem with DbContext API is that it closes and reopens connection even if you open it manually so it is a opened question if API always correctly identifies if it runs in the context of transaction and do not close connection.
#Ladislav Mrnka
You were right from the start: I have to use MSDTC.
I've tried multiple things here including the sample code I've provided.
I've tested it many times with changed hare and there but it won't work. The error goes deep into how EF and DbContext works and for that to change I'd finally find myself with my very own ORM tool. It's not the case.
I've also talked to a friend (MVP) that know a lot about EF too.
We have tested some other things here but it won't work the way I want it to. I'll end up with multiple isolated transactions (I was trying to get them together with my sample code) and with this approach I don't have any way to enforce a full rollback automatically and I'll have to create a lot of generic/custom code to manually rollback changes and here comes another question: what if this sort of rollback fails (it's not a rollback, just an update)?
So, the only way we found here is to use the MSDTC and build some tools to help debug/test if DTC is enabled, if client/server firewalls are ok and all that stuff.
Thanks anyway.
=)
So, any chance this has changed by October 19th? All over the intertubes, people suggest the following code, and it doesn't work:
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Open();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Open();
using (var transaction = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = IsolationLevel.ReadUncommitted, Timeout = TimeSpan.MaxValue}))
{
_contextA.SaveChanges();
_contextB.SaveChanges();
// Commit the transaction
transaction.Complete();
}
// Close open connections
(_contextA as IObjectContextAdapter).ObjectContext.Connection.Close();
(_contextB as IObjectContextAdapter).ObjectContext.Connection.Close();
This is a serious drag for implementing a single Unit of Work class across repositories. Any new way around this?
To avoid using MSDTC (distributed transaction):
This should force you to use one connection within the transaction as well as just one transaction. It should throw an exception otherwise.
Note: At least EF6 is required
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
}
}
Source:
http://msdn.microsoft.com/en-us/data/dn456843.aspx#existing

Resources