I am using spring-data-neo4j-4.2.0.RC1 and neo4j-ogm-core-2.1.0.jarHave the following domain objectsUser -> Role -> Priviledge
public class User extends BaseEntity{
private String firstName;
private String lastName;
#Relationship(type="ROLE")
private Role role;
}
#NodeEntity
public class Role extends BaseEntity{
private String name;
#Relationship(type="PRIVILEDGE")
private Priviledge priviledge;
}
#NodeEntity
public class Priviledge extends BaseEntity {
private String name;
}
public interface UserRepository extends GraphRepository<User> {
User findByRolePriviledgeName(String name);
}
I want to find all users that have a specific priviledge name. The above query worked with JPA and spring repository, but does not return expected result with GraphRepository. Not sure if this is a bug/not supported or I am doing something wrong
This is not supported in SDN- derived queries support only one level of nesting but yours uses two (Role, then Privilege).
You can write a custom #Query for this.
Related
I am trying to assign/save a new list with a single node and expecting it to be in the same state after the save.
Yet, if I return the Foo from Neo4j and reassign the list of Bars, it maintains a list of all Bars that were previously added.
I would expect that previous Bar nodes in Neo4j are orphaned and the Foo only has a single relationship at any given time.
How can I tell Spring Data for Neo4j to not "merge" (or whatever you call it) the list and reset the relationships exactly as the list is passed in (with one node).
Any thoughts, advice, or guidance would be greatly appreciated! Thanks!
Below, you can find a straightforward application that reproduces this problem.
#Component
public class RelationshipCommandLineListener implements CommandLineRunner {
#Autowired
private FooRepository fooRepository;
#Override
public void run(String... args) throws Exception {
fooRepository.findById("foo").defaultIfEmpty(new Foo("foo", Collections.emptyList()))
.map(foo -> {
foo.setBars(List.of(new Bar(System.currentTimeMillis())));
return foo;
})
.flatMap(foo -> fooRepository.save(foo))
.block();
}
}
#SpringBootApplication
public class Main {
#Bean(ReactiveNeo4jRepositoryConfigurationExtension.DEFAULT_TRANSACTION_MANAGER_BEAN_NAME)
public ReactiveTransactionManager reactiveTransactionManager(Driver driver, ReactiveDatabaseSelectionProvider databaseNameProvider) {
return new ReactiveNeo4jTransactionManager(driver, databaseNameProvider);
}
public static void main(String[] args) {
new SpringApplicationBuilder()
.web(WebApplicationType.NONE)
.sources(Main.class)
.build()
.run(args);
}
}
Foo, simple holds up to a list of Bars.
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Foo")
public class Foo {
#Id
String id;
private List<Bar> bars = new ArrayList<>();
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Bar")
public class Bar {
#Id
String id;
}
You need to define a relationship in your model Foo. Try this:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Node(primaryLabel = "Foo")
public class Foo {
#Id
String id;
#Relationship(type = "HAS_BARS", direction = OUTGOING)
private List<Bar> bars = new ArrayList<>();
}
My rest API is secured with OAuth2 and I want to be able to write #Queries so that only entities owned by the user are displayed. Actually, the use is part of a tenant and the entities are owned by the tenant rather than the user. The tenant identifier can be derived from the scopes in the JWT token.
My thinking was, I should be able to provide a custom SecurityExpressionRoot that takes care of deriving that tenant from the scopes and providing the value for use in the #Query annotation. This is the EvaluationContextExtension and SecurityExpressionRoot I made for this:
#Component
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
#Override
public String getExtensionId() {
return "security";
}
#Override
public SecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new CustomSecurityExpressionRoot(authentication);
}
public static class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public String getTenant() {
return "foo";
}
}
}
In the Repository, I want to be able to access the tenant property and construct the query with it:
public interface SubscriptionRepo extends CrudRepository<Subscription, Long> {
#PreAuthorize("isFullyAuthenticated()")
#Query("SELECT a FROM Subscription a WHERE a.owner = HOW_TO_ACCESS_THE_TENANT?")
#Override
Iterable<Subscription> findAll();
}
I put "HOW_TO_ACCESS_THE_TENANT?" because that's where I currently struggle. I have tried many things I found on the internet like ?#{#security.tenant}, ?#{tenant}, ?#{getTenant()}, ?#{#security.getTenant()} but nothing seems to work.
?#{#security.tenant} => SpelEvaluationException: EL1007E: Property or field 'tenant' cannot be found on null
?#{#security.getTenant()} => SpelEvaluationException: EL1011E: Method call: Attempted to call method getTenant() on null context object
?#{tenant} => SpelEvaluationException: EL1008E: Property or field 'tenant' cannot be found on object of type 'java.lang.Object[]' - maybe not public or not valid?
I am not sure if I did something wrong implementing that custom security root, or my query is wrong or maybe it just doesn't work at all that way. Can someone provide direction?
Found out properties need to be explicitly listed in a Map exposed via EvaluationContextExtension#getProperties. I have never seen this in any documentation but came across it reading one of the error messages. So the working implementation of EvaluationContextExtension with a custom SecurityExpressionRoot looks like this:
#Component
public class EditTenantEvaluationContextExtension implements EvaluationContextExtension {
#Override
public Map<String, Object> getProperties() {
Map<String, Object> properties = new HashMap<>(EvaluationContextExtension.super.getProperties());
properties.put("tenants", getRootObject().getTenants());
return properties;
}
#Override
public String getExtensionId() {
return "security";
}
#Override
public CustomSecurityExpressionRoot getRootObject() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return new CustomSecurityExpressionRoot(authentication);
}
public static class CustomSecurityExpressionRoot extends SecurityExpressionRoot {
public CustomSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
public Set<String> getTenants() {
return SecurityUtils.getTenants();
}
}
}
The query needs to look like this:
#Query("SELECT a FROM Subscription a WHERE a.owner = ?#{security.tenants}")
I have added uuid property to my SDN 4 Base entity so now it looks like:
#NodeEntity
public abstract class BaseEntity {
#GraphId
private Long id;
#Index(unique = true, primary = true)
private String uuid;
...
}
Right now some of my tests stopped working.
I have a Commentable abstract class:
#NodeEntity
public abstract class Commentable extends BaseEntity {
private final static String COMMENTED_ON = "COMMENTED_ON";
#Relationship(type = COMMENTED_ON, direction = Relationship.INCOMING)
private Set<Comment> comments = new HashSet<>();
public Set<Comment> getComments() {
return comments;
}
public void addComment(Comment comment) {
if (comment != null) {
comments.add(comment);
}
}
public void removeComment(Comment comment) {
comments.remove(comment);
}
}
Different NodeEntity from my domain model extend this class.. like
public class Decision extends Commentable
public class Product extends Commentable
etc
This my SDN 4 repository
#Repository
public interface CommentableRepository extends GraphRepository<Commentable> {
}
Right now when I try to access
commentableRepository.findOne(commentableId);
where commentableId is Decision id or Product id it is fails with a following exception:
org.springframework.dao.InvalidDataAccessApiUsageException: Supplied id does not match primary index type on supplied class.; nested exception is org.neo4j.ogm.session.Neo4jException: Supplied id does not match primary index type on supplied class.
at org.springframework.data.neo4j.transaction.SessionFactoryUtils.convertOgmAccessException(SessionFactoryUtils.java:154)
at org.springframework.data.neo4j.repository.support.SessionBeanDefinitionRegistrarPostProcessor.translateExceptionIfPossible(SessionBeanDefinitionRegistrarPostProcessor.java:71)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy175.findOne(Unknown Source)
But if I remove #Index(unique = true, primary = true) from my BaseEntity.uuid field everything starts working fine.
How to solve this issue ?
You are using the wrong Repository. GraphRepository is a legacy implementation. The new implementation is called Neo4jRepository.
Try:
public interface CommentableRepository extends Neo4jRepository<Commentable, String> {
...
}
The key difference is the second parameterized type which is the type of the ID used for the domain class; in this case String uuid.
I need to query neo4j by relationship type
this is My Entity class
#NodeEntity
#Getter
#Setter
#NoArgsConstructor
public class ProductRecommendation {
#GraphId
private Long id;
String product;
#Relationship(type = "RECOMMENDS", direction = Relationship.OUTGOING)
Set<ProductRecommendation> linkedProducts = new HashSet<>();
}
I need to find all the nodes with relationship type as "RECOMMENDS".
Is there a default findBy method?
I tried with this and it works
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:RECOMMENDS]->() RETURN p")
List<ProductRecommendation> findByRelationShipType();
}
however if I pass relationship type as variable, it doesn't work
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:{0}]->() RETURN p")
List<ProductRecommendation> findByRelationShipType(String type);
}
Can someone please explain.
Thanks
The relationship type cannot be parameterised (see http://neo4j.com/docs/developer-manual/current/cypher/#cypher-parameters).
so you will have to go with
public interface ProductRecommendationRepository extends GraphRepository<ProductRecommendation> {
#Query("MATCH p=()-[r:RECOMMENDS]->() RETURN p")
List<ProductRecommendation> findByRelationShipType();
}
I encountered some difficulties during playing with neo4j. Firstly, when I try to delete defined #EntityModel, I get an exception (Please, forgive me the quality of pics, the exception messages are also in question title):
My Controller (this is just for testing purpouse):
#Controller
public class HomeController {
#Autowired
PersonRepository personRepository;
#RequestMapping(value="/", method = RequestMethod.GET)
public String loadPage(final Model model, final HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
Person person = new Person("My person");
personRepository.save(person);
personRepository.findOne(person.getId());
return "home";
}
}
And model:
#NodeEntity
public class Person {
#GraphId
private Long id;
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public Long getId() {
return id;
}
}
Configuration file:
#Configuration
#EnableTransactionManagement
#EnableNeo4jRepositories(basePackages = "com.springapp.mvc.repository")
#ComponentScan({"com.springapp.mvc"})
public class PersistenceConfig extends Neo4jConfiguration {
#Bean
public GraphDatabaseService graphDatabaseService() {
return new SpringRestGraphDatabase("http://localhost:7474/db/data");
}
}
My Repository:
public interface PersonRepository extends GraphRepository<Person> {
#Query("MATCH (n:Person{name: \"{namveValue}\"}) RETURN n;")
Person findByName(#Param("nameValue") final String name);
}
What am I doing wrong? I figured out that perhaps Person should implement org.neo4j.graphdb.Node and this is the source of these exceptions. However, having searched github repos I see that people do not implement this interface in their models. I have not found solution on stackoverflow so far.
Node exists in database but I cannot either delete it or save it. Please, help.
You are trying to see if a node with ID '0' exists as a person. Since the root node hasn't got a '__type__' property, the call will fail. SDN uses this property to determine the entity type of a node.
That being said, the exception seems to be caused by the following line:
if(! personRepository.exists(0L)) {