I am trying to create relationships between nodes in Neo4j. I am using the Neo4J(2.1.8 Community) & spring-data-neo4j(3.3.0.RELEASE).
I am trying to create the below relation.
Create a new Employee(node) which will be reporting(Empty Relation) to the Manager(node) which is there in DB (Searching by name). I have used the below query.
public interface EmployeeRepository extends GraphRepository<Employee> {
#Query("START employee=node:({0}), manager=node:Employee(name={1}) CREATE employee-[:REPORTS_TO]->manager")
void addNewEmployee(Employee employee, String managerName);}
I got the below error.
Caused by: org.springframework.dao.InvalidDataAccessResourceUsageException: Error executing statement START employee=node:({0}), manager=node:Employee(name={1}) CREATE employee-[:REPORTS_TO]->manager; nested exception is Invalid input '(': expected whitespace or an identifier (line 1, column 21)
"START employee=node:({0}), manager=node:Employee(name={1}) CREATE employee-[:REPORTS_TO]->manager"
^
Can anyone please tell me what is wrong with this query? Also if this is not the right way to create the relations using GraphRepository then what else I can use to accomplish the same.
Thanks in advance.
Note: I have used this to learn the queries in Spring Data for Neo4j. Where they have shown the basic queries.
Updated: Employee Class
#NodeEntity
public class Employee {
#GraphId
private Long id;
private String name;
private String department;
#RelatedTo(type = "REPORTS_TO")
private Employee reportsTo;
#RelatedTo(type = "REPORTS_TO", direction = Direction.INCOMING)
Set<Employee> directReport;
public Employee() {
}
public Employee(String name, String department) {
this.name = name;
this.department = department;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public Employee getReportsTo() {
return reportsTo;
}
public void setReportsTo(Employee reportsTo) {
this.reportsTo = reportsTo;
}
public Set<Employee> getDirectReport() {
return directReport;
}
public void setDirectReport(Set<Employee> directReport) {
this.directReport = directReport;
}
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public boolean equals(Object obj) {
return super.equals(obj);
}
#Override
public String toString() {
return "Employee{" + "id=" + id + ", name=" + name + ", department=" + department + '}';
}
}
Please show the definition of employee!
You can also use template.createRelationshipBetween(nodeOrEntity,nodeOrEntity2,type)
For your example this should work:
public interface EmployeeRepository extends GraphRepository {
#Query("MATCH (employee:Employee), (manager:Employee)
WHERE id(employee) = {0} AND manager.name = {1}
CREATE employee-[:REPORTS_TO]->manager")
void addNewEmployee(Employee employee, String managerName);
}
perhaps better use, employee as a data-container / map for employee or just pass in the name directly:
#Query("MATCH (employee:Employee), (manager:Employee)
WHERE employee.name = {0}.name AND manager.name = {1}
CREATE employee-[:REPORTS_TO]->manager")
void addNewEmployee(Employee employee, String managerName);
}
Related
I am simply trying to create a Spring boot Hibernate CRUD REST API through this code:
EmployeController.java
#RestController
#RequestMapping("/api")
public class EmployeController {
#Autowired
private EmployeService employeService;
#GetMapping("/employe")
public List<Employe> get(){
return employeService.get();
}
}
Employe.java
#Entity
#Table(name="employe")
public class Employe {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column
private int id;
#Column
private String name;
#Column
private String gender;
#Column
private String department;
#Column
private Date dob;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public Date getDob() {
return dob;
}
public void setDob(Date dob) {
this.dob = dob;
}
#Override
public String toString() {
return "Employe [id=" + id + ", name=" + name + ", gender=" + gender + ", department=" + department + ", dob="
+ dob + "]";
}
}
EmployeService.java
public interface EmployeService {
List<Employe> get();
Employe get(int id);
void save(Employe employe);
void delete(int id);
}
EmployeServiceImplement.java
#Service
public class EmployeServiceImplement implements EmployeService {
#Autowired
private EmployeDAO employeDAO;
#Transactional
#Override
public List<Employe> get() {
return employeDAO.get();
}
}
EmployeDAO.java
public interface EmployeDAO {
List<Employe> get();
Employe get(int id);
void save(Employe employe);
void delete(int id);
}
EmployeDAOImplement.java
#Repository
public class EmployeDAOImplement implements EmployeDAO {
#Autowired
private EntityManager entityManager;
#Override
public List<Employe> get() {
Session currentSession = entityManager.unwrap(Session.class);
Query<Employe> query = currentSession.createQuery("from Employe", Employe.class);
List<Employe>list = query.getResultList();
return list;
}
}
I have write all the configuration related to MySQl database into the application.properties and when i run this project as Spring Boot App and go to the Postman and tried like this
and i a unable to understan why it always throws 404 error every time , can anyone tell me what i am missing in this code.
Try with this GET request, it may help you:
http://localhost:8080/api
I checked your code.
where is #RestController for your Controller file and where is #RequestMapping For your method in Controller class?
maybe you should write something like this according to your need.
tell me if you need more help.
#RestController
#RequestMapping("/api")
public class EmployeController {
#RequestMapping(value = "/employ")
public void employ() {
}
}
Instead of this -
#Override
public List get()
Use this -
#RequestMapping(value = "/Employe", method = RequestMethod.GET)
public List get()
I'm working with Spring Boot 2.0RC2 and in the documentation I read you can return a projection of an entity instead of the entity as a whole when calling the Repository. This is working fine in case I use a String in my Entity but not when I use an embedded value objects.
Let's say I have the Product entity:
#Entity
#Table(name = "t_product")
public class Product extends BaseEntity {
#Column(nullable = false)
private String name;
private Product() {}
private Product(final String name) {
this.name = name;
}
public static Result<Product> create(#NonNull final String name) {
return Result.ok(new Product(name));
}
public String getName() {
return name;
}
public void setName(#NonNull final String name) {
this.name = name;
}
}
The BaseEntity simply holds the id, created and updated attributes.
I have my projection interface called ProductSummary:
interface ProductSummary {
String getName();
Long getNameLength();
}
And in my ProductRepository I have the following method that returns the ProductSummary:
public interface ProductRepository extends JpaRepository<Product, Long> {
#Query(value = "SELECT p.name as name, LENGTH(p.name) as nameLength FROM Product p WHERE p.id = :id")
ProductSummary findSummaryById(#Param("id") Long id);
}
This works perfectly fine. Now let's say I am doing DDD and instead of using a String to represent the name attribute in the Product entity, I want to use a value object called Name:
#Embeddable
public class Name implements Serializable {
public static final int MAX_NAME_LENGTH = 100;
#Column(nullable = false, length = Name.MAX_NAME_LENGTH)
private String value;
private Name() {}
private Name(final String value) {
this.value = value;
}
public static Result<Name> create(#NonNull final String name) {
if (name.isEmpty()) {
return Result.fail("Name cannot be empty");
}
if (name.length() > MAX_NAME_LENGTH) {
return Result.fail("Name cannot be longer than " + MAX_NAME_LENGTH + " characters");
}
return Result.ok(new Name(name));
}
public String getValue() {
return value;
}
}
I change my Product entity to:
#Entity
#Table(name = "t_product")
public class Product extends BaseEntity {
#Embedded
private Name name;
private Product() {}
private Product(final Name name) {
this.name = name;
}
public static Result<Product> create(#NonNull final Name name) {
return Result.ok(new Product(name));
}
public Name getName() {
return name;
}
public void setName(final Name name) {
this.name = name;
}
}
And in the ProductSummary I change the return type from String to Name.
When I run that I always get the exception:
Caused by: java.lang.IllegalAccessError: tried to access class com.acme.core.product.ProductSummary from class com.sun.proxy.$Proxy112
Can I make this work or am I missing some restriction which doesn't allow this?
If you wish to get the complete Name field(not a particular field in Name class), then you need to create another interface like ProductSummary.
interface ProductSummary {
NameSummary getName();
interface NameSummary {
String getValue();
}
}
No need to change anything in your repository.
It is quite clearly documented here
And make sure your interfaces and the methods are public.
Problem: it works till i try to add Student obejcts to Database, but the tables are being created correctly .
I can't simplify the post any further. But it's mainly code that doesn't require a lot of reading, it's a simple spring data repository service model. I posted it all due to the fact idk what am i doing wrong. Problem is in the JPA mapping.
I got the example from over here http://www.java2s.com/Tutorial/Java/0355__JPA/OneToManyBidirectional.htm
MDOELS
#Entity
public class Department {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy="department")
private Collection<Student> students;
public Department() {
}
public Department(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String deptName) {
this.name = deptName;
}
public Collection<Student> getStudents() {
return students;
}
public void setStudent(Collection<Student> students) {
this.students = students;
}
public String toString() {
return "Department id: " + getId() +
", name: " + getName();
}
}
#Entity
public class Student {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
private String name;
#ManyToOne (cascade=CascadeType.ALL)
private Department department;
public Student() {
}
public Student(String name, Department department) {
this.name = name;
this.department = department;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String toString() {
return "\n\nID:" + id + "\nName:" + name + "\n\n" + department;
}
}
REPOSITORIES
#Repository
public interface DepartmentRepository extends JpaRepository<Department, Integer> {
Department findByName(String name);
}
#Repository
public interface StudentRepository extends JpaRepository<Student, Integer> {
Student findByName(String name);
}
SERVICES
#Service
public class StudentService {
private final StudentRepository studentRepository;
#Autowired
public StudentService(StudentRepository studentRepository) {
this.studentRepository = studentRepository;
}
public void addToDatabase(Student student) {
this.studentRepository.saveAndFlush(student);
}
public Student getStudentByName(String name) {
return studentRepository.findByName(name);
}
}
#Service
public class DepartmentService {
private final DepartmentRepository departmentRepository;
#Autowired
public DepartmentService(DepartmentRepository departmentRepository) {
this.departmentRepository = departmentRepository;
}
public void addToDataBase(List<Department> department) {
this.departmentRepository.save(department);
department.forEach(this.departmentRepository::saveAndFlush);
}
public Department getDepartmentByName(String name){
return this.departmentRepository.findByName(name);
}
}
My main method
#Component
public class Terminal implements CommandLineRunner {
private final StudentService studentService;
private final DepartmentService departmentService;
#Autowired
public Terminal(StudentService studentService, DepartmentService departmentService) {
this.studentService = studentService;
this.departmentService = departmentService;
}
#Override
public void run(String... strings) throws Exception {
Department department = new Department("dep1");
Department department1 = new Department("dep2");
Department department2 = new Department("dep3");
Department department3 = new Department("dep4");
List<Department> departments = new ArrayList<>(Arrays.asList(department, department1, department2, department3));
this.departmentService.addToDataBase(departments);
//
Student student = new Student("pesho", department);
Student student11 = new Student("gosho", department1);
this.studentService.addToDatabase(student11);
this.studentService.addToDatabase(student);
student = new Student("sasho", department2);
this.studentService.addToDatabase(student);
// System.out.println(this.studentService.getStudentByName("gosho").getDepartment1());
// System.out.println("CHECKING ONE TO ONE BIDIRECTIONAL: " + this.departmentService.getDepartmentByName("dep1").getStudent());
}
}
So here when i try to add students in the students table it gives an error
The error is the fallowing
Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: app.models.Department
you added cascade= CascadeType.ALL for Department in Student class and save departments separete. this.departmentService.addToDataBase(departments);
fix : dont call
departmentService.addToDataBase(departments);
or remove CascadeType.ALL from Student
Well I can't understand you problem completely but here's what I would like to add. Cascading for add operation is not implemented or it's incomplete. Hope it helps.
While there is plenty of information around on how to model, in JPA (2), a one-to-one relationship OR an entity having a natural key, I haven't been able to find a clear / simple answer to how to model the situation where we have both, i.e. a one-to-one relationship where the parent table has a natural key. It could obviously be that I might have missed such a tutorial; if so, pointing me to one could also be the answer.
And, as many times with JPA and noobs such as I, the moment one needs a bit more than the most basic model, one can quickly hit the wall.
Hence, considering the following DB model:
What would be the corresponding JPA-annotated object model? (I'm sparing you guys of the things I've tried since I don't want to influence the answer...)
Performance recommendations are also welcome (e.g. "a one-to-many could perform faster", etc.)!
Thanks,
The composite identifier is built out of two numerical columns so the mapping looks like this:
#Embeddable
public class EmployeeId implements Serializable {
private Long companyId;
private Long employeeId;
public EmployeeId() {
}
public EmployeeId(Long companyId, Long employeeId) {
this.companyId = companyId;
this.employeeId = employeeId;
}
public Long getCompanyId() {
return companyId;
}
public Long getEmployeeId() {
return employeeId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof EmployeeId)) return false;
EmployeeId that = (EmployeeId) o;
return Objects.equals(getCompanyId(), that.getCompanyId()) &&
Objects.equals(getEmployeeId(), that.getEmployeeId());
}
#Override
public int hashCode() {
return Objects.hash(getCompanyId(), getEmployeeId());
}
}
The parent class, looks as follows:
#Entity(name = "Employee")
public static class Employee {
#EmbeddedId
private EmployeeId id;
private String name;
#OneToOne(mappedBy = "employee")
private EmployeeDetails details;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public EmployeeDetails getDetails() {
return details;
}
public void setDetails(EmployeeDetails details) {
this.details = details;
}
}
And the child like this:
#Entity(name = "EmployeeDetails")
public static class EmployeeDetails {
#EmbeddedId
private EmployeeId id;
#MapsId
#OneToOne
private Employee employee;
private String details;
public EmployeeId getId() {
return id;
}
public void setId(EmployeeId id) {
this.id = id;
}
public Employee getEmployee() {
return employee;
}
public void setEmployee(Employee employee) {
this.employee = employee;
this.id = employee.getId();
}
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
}
}
And everything works just fine:
doInJPA(entityManager -> {
Employee employee = new Employee();
employee.setId(new EmployeeId(1L, 100L));
employee.setName("Vlad Mihalcea");
entityManager.persist(employee);
});
doInJPA(entityManager -> {
Employee employee = entityManager.find(Employee.class, new EmployeeId(1L, 100L));
EmployeeDetails employeeDetails = new EmployeeDetails();
employeeDetails.setEmployee(employee);
employeeDetails.setDetails("High-Performance Java Persistence");
entityManager.persist(employeeDetails);
});
doInJPA(entityManager -> {
EmployeeDetails employeeDetails = entityManager.find(EmployeeDetails.class, new EmployeeId(1L, 100L));
assertNotNull(employeeDetails);
});
doInJPA(entityManager -> {
Phone phone = entityManager.find(Phone.class, "012-345-6789");
assertNotNull(phone);
assertEquals(new EmployeeId(1L, 100L), phone.getEmployee().getId());
});
Code available on GitHub.
I am writing some test code to learn spring-data with MongoDB. I can successfully create two Documents: Person and ADocument, where ADocument contains a reference to Person.
#Document
public class Person {
#Id
private ObjectId id;
#Indexed
private String name;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
...
#Document
public class ADocument {
#Id
private ObjectId id;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
private String title;
private String text;
#DBRef
private Person docperson;
public Person getDocperson() {
return docperson;
}
public void setDocperson(Person docperson) {
this.docperson = docperson;
}
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
}
The problem arises when I try to get all the 'adocuments' related to a person by using the person's ID (once the person's name is provided):
public List<ADocument> loadDocumentsByPersonName(String pname) {
Query qPerson = new Query().addCriteria(Criteria.where("name").is(pname));
qPerson.fields().include("_id");
Person pers = mongoTemplate.findOne(qPerson, Person.class);
ObjectId persId = pers.getId();
Query qDoc = new Query().addCriteria(Criteria.where("person.$id").is(persId));
System.out.println(qDoc.toString());
List<ADocument> list2 = mongoTemplate.find(qDoc, ADocument.class);
return list2;
}
Everyting works fine except that list2 is always empty (while it shouldn't).
System.out.println(qDoc.toString()) gives something like:
Query: { "person.$id" : { "$oid" : "536a0d50e4b0d0c10297f2ab"}}, Fields: null, Sort: null
If I try to issue the query above on the Mongo shell I get the following:
db.adocument.find({ "person.$id" : { "$oid" : "536a0805e4b0af174d0b5871"}})
error: {
"$err" : "Can't canonicalize query: BadValue unknown operator: $oid",
"code" : 17287
}
While if I type
db.adocument.find({ "person.$id" : ObjectId("536a0805e4b0af174d0b5871")})
I actually get a result!
I am using MongoDB 2.6.0 and Spring Data 1.4.2.
I really can't figure out what's going on... Any help is extremely appreciated!
I got it!
For some reason, I had to explicit the collection name in the Query:
List list2 = mongoTemplate.find(qDoc, ADocument.class, COLLECTION_NAME);
where COLLECTION_NAME="adocument".
As for the shell behaviour, it seems that Query.toString() does never return a correct syntax to be cut and paste for shell execution.