In my Spring Data Neo4j 4 project - BeforeSaveEvent class is deprecated.
Also, previously I used a following code in order to setup created/updated date for my entities:
#EventListener
public void handleBeforeSaveEvent(BeforeSaveEvent event) {
Object entity = event.getEntity();
if (entity instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) entity;
if (baseEntity.getCreateDate() == null) {
baseEntity.setCreateDate(new Date());
} else {
baseEntity.setUpdateDate(new Date());
}
}
}
but right now this listener is not invoked.
Is there any replacement for this logic in Neo4j 4 ? I'll really appreciate an example. Thanks
UPDATED
The configuration described below is working but some of my tests are fail because of NULL dates on a previously saved entities.. something is still wrong..
After clarification found a reason of this issue and waiting for this bugfix Modifications during a onPreSave() event do not persist to the database
#Configuration
#EnableExperimentalNeo4jRepositories(basePackages = "com.example")
#EnableTransactionManagement
public class Neo4jTestConfig {
#Bean
public Neo4jTransactionManager transactionManager() throws Exception {
return new Neo4jTransactionManager(sessionFactory());
}
#Bean
public SessionFactory sessionFactory() {
return new SessionFactory("com.example") {
#Override
public Session openSession() {
Session session = super.openSession();
session.register(new EventListenerAdapter() {
#Override
public void onPreSave(Event event) {
Object eventObject = event.getObject();
if(eventObject instanceof BaseEntity) {
BaseEntity baseEntity = (BaseEntity) eventObject;
if (baseEntity.getCreateDate() == null) {
baseEntity.setCreateDate(new Date());
} else {
baseEntity.setUpdateDate(new Date());
}
}
}
});
return session;
}
};
}
}
You must be using Spring Data Neo4j (SDN) 4.2.0.M1. This has not been officially released yet but you are free to test it while it is undergoing the Spring Data Release process.
The event code in SDN has been deprecated in favour of a variety of mechanisms. Number one is that Spring Data now support Transaction aware event listeners. You can check out how to implement those here.
Number two is that you can now autowire the Neo4j OGM Session into your application and take advantage of it's event capabilities (see the register() method).
Finally you are able to marry the two concepts up together and get OGM generated events fired through Spring!
Documentation will come as we continue the release but for now feel free to play with it yourself.
Related
I have a really nasty StackOverflowException in my spring backend, that I need help with. This is not going to be solved easily. I really hope to find some help here.
Most parts of my backend work. I can query my REST interface for models, they are nicely returned by spring-hateoas, GET, PUT and POST operations work. But one exception: When I try to update an existing DelegationModel, then I run into an endless StackOverflowException.
Here is my DelegetionModel.java class. Please mark, that delegation model actually doesn't have any property annotated with #CreatedBy!
#Entity
#Data
#NoArgsConstructor
#RequiredArgsConstructor(suppressConstructorProperties = true) //BUGFIX: https://jira.spring.io/browse/DATAREST-884
#EntityListeners(AuditingEntityListener.class) // this is necessary so that UpdatedAt and CreatedAt are handled.
#Table(name = "delegations")
public class DelegationModel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long id;
/** Area that this delegation is in */
#NonNull
#NotNull
#ManyToOne
public AreaModel area;
/** reference to delegee that delegated his vote */
#NonNull
#NotNull
#ManyToOne
public UserModel fromUser;
/** reference to proxy that receives the delegation */
#NonNull
#NotNull
#ManyToOne
public UserModel toProxy;
#CreatedDate
#NotNull
public Date createdAt = new Date();
#LastModifiedDate
#NotNull
public Date updatedAt = new Date();
}
As described in the Spring-data-jpa doc I implemented the necessary AuditorAware interface, which loads the UserModel from the SQL DB. I would have expected that this AuditorAware interface is only called for models that have a field annotated with #CreatedBy.
#Component
public class LiquidoAuditorAware implements AuditorAware<UserModel> {
Logger log = LoggerFactory.getLogger(this.getClass()); // Simple Logging Facade 4 Java
#Autowired
UserRepo userRepo;
#Override
public UserModel getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
log.warn("Cannot getCurrentAuditor. No one is currently authenticated");
return null;
}
User principal = (org.springframework.security.core.userdetails.User) authentication.getPrincipal();
UserModel currentlyLoggedInUser = userRepo.findByEmail(principal.getUsername()); // <<<<======= (!)
return currentlyLoggedInUser;
} catch (Exception e) {
log.error("Cannot getCurrentAuditor: "+e);
return null;
}
}
}
Now I update a DelegationModel in my UserRestController. The functional "Scrum User Story" here is:
As a user I want to be able to store a delegation so that I can forward my right to vote to my proxy.
#RestController
#RequestMapping("/liquido/v2/users")
public class UserRestController {
[...]
#RequestMapping(value = "/saveProxy", method = PUT, consumes="application/json")
#ResponseStatus(HttpStatus.CREATED)
public #ResponseBody String saveProxy(
#RequestBody Resource<DelegationModel> delegationResource,
//PersistentEntityResourceAssembler resourceAssembler,
Principal principal) throws BindException
{
[...]
DelegationModel result = delegationRepo.save(existingDelegation);
[...]
}
[...]
}
For some reason, that I cannot see, this actualy calls the AuditorAware implementation above. The problem is now, that my LqiuidoAuditorAware implementation is called again and again in and endless loop. It seems that the query for the UserModel inside LiquidoAuditorAware.java calls the LiquidoAuditorAware again. (Which is unusual, because that is only a read operation from the DB.)
Here is the full ThreadDump as a Gist
All the code can by found in this github repo
I'd really apriciate any help here. I am searching in the dark :-)
The reason for the behavior you see is that the AuditorAware implementation is called from within a JPA #PrePersist/#PreUpdate callback. You now issue a query by calling findByEmail(…), which triggers the dirty-detection again, which in turn causes the flushing to be triggered and thus the invocation of the callbacks.
The recommended workaround is to keep an instance of the UserModel inside the Spring Security User implementation (by looking it up when the UserDetailsService looks up the instance on authentication), so that you don't need an extra database query.
Another (less recommended) workaround could be to inject an EntityManager into the AuditorAware implementation, call setFlushMode(FlushModeType.COMMIT) before the query execution and reset it to FlushModeType.AUTO after that, so that the flush will not be triggered for the query execution.
I twist myself around a workable solution to use several databases in RavenDB for an ASP.Net MVC app using Castle Windsor for the wiring.
This is the current installer
public class RavenInstaller : IWindsorInstaller
{
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(
Component.For<IDocumentStore>().Instance(CreateDocumentStore()).LifeStyle.Singleton,
Component.For<IDocumentSession>().UsingFactoryMethod(GetDocumentSesssion).LifeStyle.PerWebRequest
);
}
static IDocumentStore CreateDocumentStore()
{
var store = new DocumentStore { ConnectionStringName = "RavenDb_CS9" };
store.Initialize();
IndexCreation.CreateIndexes(typeof(Users).Assembly, store);
return store;
}
static IDocumentSession GetDocumentSesssion(IKernel kernel)
{
var store = kernel.Resolve<IDocumentStore>();
return store.OpenSession();
}
}
The above works perfect but only for one Database.
I can't find the proper thinking how to handle another database. The whole chain starts with a domain service asking for an IDocumentSession. Then the flow is as specified in the above installer. But where/how do I ask for a "SessionToDb1" or a "SessionToDb2"?
The important is of course what connection string to use (where the DB property is specified) but also what indexes to create in respective DB / DocumentStore.
Did anyone accomplish this using Windsor? Am I thinking/attacking it wrong here?
Thanks!
Because you have:
Component.For<IDocumentSession>()
.UsingFactoryMethod(GetDocumentSesssion)
.LifeStyle.PerWebRequest
Your GetDocumentSession method is going to be called any time you inject an IDocumentSession. This is good.
When working with multiple databases, you need to pass the database name as a parameter to OpenSession. So, you need some way to resolve which database you would like to connect to based on the current web request.
You need to modify the GetDocumentSession method to implement whatever custom logic you are going to use. For example, you may want to look at a cookie, asp.net session item, current thread principal, or some other criteria. The decision is custom to your application, all that matters is somehow you open the session with the correct database name.
I've run into this problem before with nhibernate.
I found the best solution is to create a SessionManager class which wraps the Creation of the document store and the Session..
So I.E.
public interface ISessionManager
{
void BuildDocumentStore();
IDocumentSession OpenSession();
}
public interface ISiteSessionManager : ISessionManager
{
}
public class SiteSessionManager : ISiteSessionManager
{
IDocumentStore _documentStore;
public SiteSessionManager()
{
BuildDocumentStore();
}
public void BuildDocumentStore()
{
_documentStore = new DocumentStore
{
Url = "http://localhost:88",
DefaultDatabase = "test"
};
_documentStore.Initialize();
IndexCreation.CreateIndexes(typeof(SiteSessionManager).Assembly, _documentStore);
}
public IDocumentSession OpenSession()
{
return _documentStore.OpenSession();
}
}
// And then!.
Container.Register(Component.For<ISiteSessionManager>().Instance(new SiteSessionManager()).LifestyleSingleton());
// And then!.
public class FindUsers
{
readonly ISiteSessionManager _siteSessionManager;
public FindUsers(ISiteSessionManager siteSessionManager)
{
_siteSessionManager = siteSessionManager;
}
public IList<User> GetUsers()
{
using (var session = _siteSessionManager.OpenSession())
{
// do your query
return null;
}
}
}
Rinse and repeat for multiple databases.!
I have the following code:
JSF Managed Bean:
#ManagedBean(name = "purchaseView")
#ViewScoped
public class PurchaseView implements Serializable {
#EJB
private PurchaseService service;
private Order order;
// Getter/Setters here
public void checkoutOrder() {
// .. some checks for null here, then call service
service.checkout(order);
}
}
Service:
#Stateless
public class BuyVoucherService {
#EJB
private OrderBean orderBean;
#EJB
private ProductBean productBean;
public boolean checkout(Order order) {
orderBean.create(order);
for(int i=0;i<order.getQuantity();i++) {
Product product = new Product();
if(someCondition) {
// don't save anything and
return false;
}
// .. some setter here
product.setOrder(order);
productBean.create(product);
}
return true;
}
The productBean and orderBean are simple JPA EJB with the EntityManager and CRUD operation (Generated by Netbeans..).
In the Service above, things are persisted in the database when the Service returns. In the case something is wrong (someCondition == TRUE above), if I return false the orderBean.save(order) will still persist the order in the database, and I don't want that.
Is throwing an EJBException and catching it in the ManagedBean the best option?
As you haven't specified any transaction attribute explicitly, it will be most probably Required, but depends on the server. Therefore both these methods will be within same transaction, so rolling back in a method will cascade the changes in another.
You can also try using Mandatory attribute for the 2nd method, it will ensure that it requires a transaction to proceed further, else will cause runtime exception.
#Resource
private EJBContext context;
try{
if(someCondition) {
throw SomeBusinessException("Failed, rolling back");
}
}catch(Exception e){
log(e.getMessage, e)
context.setRollbackOnly();
}
Else, you can throw system exception, that will force the container to rollback the changes being made.
if(someCondition)
throw SomeBusinessException("Failed, rolling back");
}catch(Exception e){
throw new EJBException (e.getMessage(), e);
}
Busy doing some work on an existing web app and concerned about the thread safety of the ObjectContext being used in a BaseRepository class. The code that is causing my spidey sense to tingle is:
// within base repository
private SiteDataContext context;
public SitepDataContext Context
{
get
{
if (context == null)
context = new SiteDataContext();
return context;
}
}
// inherited repository
public class InheritedRepository1 : BaseRepository
{
public SomeEntity Get()
{
var something = Context.SomeEntity.First();
}
}
public class InheritedRepository2 : BaseRepository
{
public SomeOtherEntity Get()
{
var something = Context.SomeOtherEntity.First();
}
}
My understanding is:
the ObjectContext is not threadsafe and may be shared across threads in this instance.
A single objectcontext should be used across an http request. Multiple objectcontexts are being created from various repositories to render a page.
The objectcontext does not seem to be closed, disposed off at any point in the http request. This could be a problem if transactions are being used and transactions are committed from threads than did not begin them.
Would appreciate any feedback on these 3 points above as my experience is primarily based on NHibernate.
You could implement the Repository and Unit of Work patterns.
Considering the IIS uses Thread pool to manage requests, my solution is to create one and only one ThreadStatic DataContext for each request, and clear it after request ending.
public class DataContextManager
{
[ThreadStatic]
private static MyDataContext dataContext = null;
public static MyDataContext GetContext()
{
if (dataContext == null)
{
dataContext = new MyDataContext();
}
return dataContext;
}
public static void Clear()
{
dataContext = null;
}
}
I have implemented a service which uses a DAOFactory and a NHibernate Helper for the sessions and transactions. The following code is very much simplified:
public interface IService
{
IList<Disease> getDiseases();
}
public class Service : IService
{
private INHibernateHelper NHibernateHelper;
private IDAOFactory DAOFactory;
public Service(INHibernateHelper NHibernateHelper, IDAOFactory DAOFactory)
{
this.NHibernateHelper = NHibernateHelper;
this.DAOFactory = DAOFactory;
}
public IList<Disease> getDiseases()
{
return DAOFactory.getDiseaseDAO().FindAll();
}
}
public class NHibernateHelper : INHibernateHelper
{
private static ISessionFactory sessionFactory;
/// <summary>
/// SessionFactory is static because it is expensive to create and is therefore at application scope.
/// The property exists to provide 'instantiate on first use' behaviour.
/// </summary>
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
try
{
sessionFactory = new Configuration().Configure().AddAssembly("Bla").BuildSessionFactory();
}
catch (Exception e)
{
throw new Exception("NHibernate initialization failed.", e);
}
}
return sessionFactory;
}
}
public static ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
public static void DisposeSession()
{
var session = GetCurrentSession();
session.Close();
session.Dispose();
}
public static void BeginTransaction()
{
GetCurrentSession().BeginTransaction();
}
public static void CommitTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Commit();
}
public static void RollbackTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Rollback();
}
}
At the end of the day I just want to expose the IService to ASP.NET MVC/Console application/Winform. I can already use the Service in a console application but would like to improve it first. I guess the first improvement would be to inject the interfaces INHibernateHelper and IDAOFactory via castle. But I think the problem is that the NHibernateHelper might cause problems in a asp.net context where NHibernateHelper should run according to the 'Nhibernate session per request' pattern. One question I have is whether this pattern is determined by the nhibernate config section (setting current_session_context_class = web) or can i control this via castle somehow?
I hope this makes sense. The final aim is just to expose THE IService.
Thanks.
Christian
You have two choices..
1) Host it in WCF. This allows you access from any source you want.
2) Abstract away everything that's specific to how the code is being used. In our system for instance we use our own Unit Of Work implementation which is stored differently based on where the code is running. A small example would be storing something using the WCF call context vs. the current thread.