Spring Data Neo4j: Complex relationships not persisting - java

When adding multiple relationships between nodes simultaneously, only some of them are created. In the example below, the calls to makeUser(...) are only populating some of the relationships.
Main
#Transactional
void clearDatabase() {
session.execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r");
}
void createPeople() {
Person mark = new Person("Mark");
mark.password = "mark123";
people.save(mark);
Organisation noxRentals = new Organisation("Nox Rentals");
organisations.save(noxRentals);
makeUser(noxRentals, mark, "Administrator", Right.ADMINISTRATE);
makeUser(noxRentals, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, mark, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, charlotte, "Reservations", Right.LOGIN, Right.SEND_QUOTES);
}
#Transactional
void makeUser (Organisation organisation, Person person, String role, Right...rights) {
IsUser account = organisation.addUser(person, role);
account.addRights(rights);
organisations.save(organisation);
}
void run() {
clearDatabase();
createPeople();
}
Resulting in (notice Nox has no relationships):
Organisation.java
#NodeEntity
public class Organisation extends NitroBaseEntity {
#Relationship(type = "IsUser", direction = Relationship.INCOMING)
Set<IsUser> users = new HashSet<>();
public IsUser addUser(Person person, String role) {
IsUser user = new IsUser(person, this, role);
this.users.add(user);
return user;
}
}
Person.java
#NodeEntity
public class Person extends NitroBaseEntity {
#Property
String password;
#Relationship(type = "IsUser", direction = Relationship.OUTGOING)
Set<IsUser> users = new HashSet<>();
public Set<IsUser> getUserAccounts() {
return this.users;
}
}
IsUser.java
#RelationshipEntity
public class IsUser {
#GraphId
Long id;
#StartNode
public Person person;
#EndNode
public Organisation organisation;
#Property
public String role;
#Property
public Set<Right> rights = new HashSet<>();
public IsUser (Person person, Organisation organisation, String role) {
this.person = person;
this.organisation = organisation;
this.role = role;
}
}
Complete source code: https://bitbucket.org/sparkyspider/neo4j-sandbox-4/src/22eb3aba82e33dfe473ee15e26f9b4701c62fd8e/src/main/java/com/noxgroup/nitro/config/DatabaseInitializer.java?at=master

There are two things missing-
The type has to be specified on the #RelationshipEntity as well, like this #RelationshipEntity(type = "IsUser")
In Organisation.addUser(), add the IsUser to the Person too, something like person.users.add(user);. The entities have to be navigable from both ends.

Related

Spring Boot DBRef is null after saving

I have user and role models. Thats look like this:
User:
#Document("user")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
private String id;
private String name;
private String username;
private String password;
#DBRef
private Collection<Role> roles = new ArrayList<>();
Role:
#Document("role")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Role {
#Id
private String id;
private String name;
}
And I have besides others those three methods to save user, save role and add role to user, like this:
#Override
public User saveUser(User user) {
log.info("Saving new user {} to the database", user.getName());
return userRepository.save(user);
}
#Override
public Role saveRole(Role role) {
log.info("Saving new role {} to the database", role.getName());
return roleRepository.save(role);
}
#Override
public void addRoleToUser(String username, String roleName) {
log.info("Adding role {} to user {}", roleName, username);
User user = userRepository.findByUsername(username);
Role role = roleRepository.findByName(roleName);
user.getRoles().add(role);
}
Also I have a CommandLineRunner to insert those data
#Bean
CommandLineRunner run(UserService userService) {
return args -> {
userService.saveRole(new Role(null, "ROLE_USER"));
userService.saveRole(new Role(null, "ROLE_ADMIN"));
userService.saveUser(new User(null,"Stefan", "stefanadmin", "stefanadmin", new ArrayList<>()));
userService.saveUser(new User(null,"Marko", "markoadmin", "markoadmin", new ArrayList<>()));
userService.saveUser(new User(null,"Jovan", "jovanobicanuser", "jovanobicanuser", new ArrayList<>()));
userService.addRoleToUser("stefanadmin", "ROLE_ADMIN");
userService.addRoleToUser("stefanadmin", "ROLE_USER");
userService.addRoleToUser("markoadmin", "ROLE_ADMIN");
userService.addRoleToUser("jovanobicanuser", "ROLE_USER");
};
}
And everything works fine except array column roles, its empty.
Its like this in JSON when I try to return all users:
{"id":"6331bda42c1fa17e41079c99","name":"Jovan","username":"jovanobicanuser","password":"jovanobicanuser","roles":[]}]
I'm following some tutorial for this stuff, guy is using MySql while I'm using Mongo, so I think problem is somewhere how I'm trying to connect those two tables.
What can cause problem like this, what I'm missing?

How should I go about creating this project using REST API?

I have to create a very simple Spring "market" app.
No front-end needed
The Market:
The system must operate as a simplified market where users can be buyers or sellers.
Users:
user entity attributes: id:1, username:"User1", account:0
//account just gets incremented with each entry in the database.
The users can buy and sell items.
Items:
item entity attributes: id:3, name:Item1, ownerId:1.
example for interacting with items endpoints:
create: {id:1 name:"Item1", ownerId:1};
getAllItems with ownerId = 1 (use single query)
[
{
"id":3,
"name":”Item1”,
"ownerId":1,
“ownerUsername”:"User1"
}
]
Example:
"User1" owns "Item1". He wants to sell it for $100. He creates an active contract. Other users can review all active contracts and choose to participate. "User2" has enough money in her account and buys "Item1". The contract is now closed. "User1" receives $100 in his account. "User2" is the new owner of "Item1".
Contracts:
contract entity attributes: id, sellerId, buyerId, itemId, price,status. (The seller is the owner of the item and can not be the buyer)
endpoints - CRUD. Example for interacting with contracts endpoints:
create: {itemId : 3, price : 100}. Expected behavior: find the owner of item with id 3 in the DB (ownerId = 1) persist the new active contract in the DB:
{
"sellerId":1,
"itemId":3,
"price":100,
"active":true
}
update price of active contract by id: {"itemId":3, "price":200}
getAllActive contracts (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"itemId":3,
"price":200,
"active":true
}
]
closing active contract by id {"itemId":3, "buyerId":2}.
Expected behavior: update the accounts of users with id 1 and id 2.
getAllClosed contracts by optional parameters: itemId, sellerId, buyerId (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"buyerId":2,
“buyerUsername”:"User2",
"itemId":3,
"price":100,
"active":false
}
]
So far, these are my Entities:
BaseEntity:
#MappedSuperclass
public abstract class BaseEntity {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Users:
#Entity
#Table(name = "users")
public class User extends BaseEntity{
private String username;
private Long account;
private Set<Item> items;
public User() {
}
#Column(name = "username", nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "account", nullable = false)
public Long getAccount() {
return account;
}
public void setAccount(Long account) {
this.account = account;
}
#OneToMany(mappedBy = "id")
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
Items:
#Entity
#Table(name = "items")
public class Item extends BaseEntity{
private String name;
private String ownerUsername;
private User user;
public Item() {
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//get the id of the item's owner
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getOwnerUsername() {
return user.getUsername();
}
public void setOwnerUsername(String ownerUsername) {
this.ownerUsername = ownerUsername;
}
}
So, what should I do from here on?
If you've already created persistence layers (using Spring Data JPA or another mapper), You need to develop service logic and create a presentation layer.
like this (just user domain)
UserService (service layer)
#Service
#RequiredArgsConstructor
public class UserService {
private final UserJpaRepository repository;
#Transactional
public Long createUser(String username) {
User user = new User();
user.setUsername(username);
// other logic ...
repository.save(user);
return user.getId();
}
#Transactional(readonly = true)
public User getUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> IllegalArgumentsException("Not Found Entity."))
}
}
UserAPIController (presentation layer)
#RestController
#RequiredArgsConstructor
public class UserAPIController {
private final UserService userService;
#PostMapping("/users")
public ResponseEntity<Long> createUser(#RequestBody CreateUserDTO dto) {
Long userId = userService.createUser(dto.getUsername());
return new ResponseEntity(userId, HttpStatus.CREATED);
}
#GetMapping("/users/{id}")
public ResponseEntity<User> getUser(#PathVariable Long id) {
User user = userService.getUser(id);
return new ResponseEntity(user, HttpStatus.OK);
}
}

Saving to Neo4j removing values

I am using a typical User -> Role relationship with my application. I am taking advantage of Neo4j's ability to add metadata on the relationship through the RelationshipEntity annotation. For some unexplained reason the Role's properties are being 'nulled' out when I persists my relationship.
Here's my User
public class Person {
#GraphId Long id;
private String name;
#RelatedToVia(type="HAS_ROLE")
#Fetch
private Set<UserRoleRelationship> roles = new HashSet<UserRoleRelationship>();
public Person() {}
public Person(String name) { this.name = name; }
}
Role
#NodeEntity
public class Role {
#GraphId Long id;
private RoleType roleType;
#SuppressWarnings("unused")
private String roleName;
private #Fetch Set<Privilege> defaultPrivileges;
}
RelationshipEntity
#RelationshipEntity(type="HAS_ROLE")
public class UserRoleRelationship {
#GraphId Long id;
#StartNode private Person person;
#EndNode private Role role;
private List<Privilege> privileges = new ArrayList<Privilege>();
}
RoleType (Used in Role)
public enum RoleType {
ADMIN("Administrator", Arrays.asList(new Privilege(PrivilegeType.PRIV1.name()),
new Privilege(PrivilegeType.PRIV2.name()),
new Privilege(PrivilegeType.PRIV3.name()),
new Privilege(PrivilegeType.PRIV4.name()))),
USER("User", Arrays.asList(new Privilege(PrivilegeType.PRIV1.name()))),
private String name;
private Set<Privilege> defaultPrivileges = new HashSet<Privilege>();
private RoleType(String name, List<Privilege> privileges){
this.name=name;
this.defaultPrivileges=new HashSet<Privilege>(privileges);
}
public String getName() {
return name;
}
public Set<Privilege> getDefaultPrivileges() {
return defaultPrivileges;
}
}
When populating the relationship with a User and Role, all the data is set. However when I persists it the Role's data is now null.
// Before Save
UserRoleRelationship [id=null, person=Person [id=26, name=TestUser, roles=1], **role=Role [id=27, roleType=ADMIN, defaultPrivileges=[PRIV4, PRIV1, PRIV2, PRIV3]]**]
// After Save
UserRoleRelationship [id=6, person=Person [id=26, name=TestUser, roles=0], **role=Role [id=27, roleType=null, defaultPrivileges=null]**]
Any ideas as to why this is occurring to my Role Object?
Also, here are my Repos
public interface PersonRepository extends CrudRepository<Person, String> {
Person findByName(String name);
}
public interface RoleRepository extends CRUDRepository<Role> {
Role findByRoleType(RoleType rt);
Role findByRoleName(String name);
}
public interface UserRoleRepository extends CrudRepository<UserRoleRelationship, String> {}
It repopulates the UserRoleRelationship after saving.
As you don't have a #Fetch annotation on neither the start nor end-node it only loads them in a shallow fashion.
I don't recommend to add #Fetch there because it can easily load your whole graph, so I'd probably use
template.fetch(rel.person);
template.fetch(rel.role);

How to handle join query in Hibernate and Spring with annotations?

I am developing application using Spring and Hibernate with MySQL. I am new to Hibernate and did basic tasks...
Now I need to apply joins in select query to get data from multiple table using annotations. I have searched for it but still I didn't get any idea...
Here my database tables and bean classes :
Table 1: 'employee_info' ( id, empid, empname, doj and jobtitle )
Table 2: 'employee_login' ( username, password, status and empid )
And my bean classes are:
EmployeeInfoForm.java
#Entity()
#Table(name = "employee_info")
public class EmployeeInfoForm {
#Id
#GeneratedValue
#Column(name = "id", unique = true, nullable = true)
private int id;
#Column(name = "empId")
private int empId;
#Column(name = "empname")
private String empName;
#Column(name = "doj")
private Date empDoj;
#Column(name = "jobtitle")
private String empJobTitle;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public void setEmpDoj(Date empDoj) {
this.empDoj = empDoj;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public Date getEmpDoj() {
return empDoj;
}
public void setEmp_Doj(Date empDoj) {
this.empDoj = empDoj;
}
public String getEmpJobTitle() {
return empJobTitle;
}
public void setEmpJobTitle(String empJobTitle) {
this.empJobTitle = empJobTitle;
}
}
EmployeeLoginForm.java
#Entity()
#Table(name = "employee_login")
public class EmployeeLoginForm {
#Id
#Column(name = "username")
private String empUserName;
#Column(name = "password")
private String empPassword;
#Column(name = "status")
private String empStatus;
#Column(name = "empid")
private int empId;
public String getEmpUserName() {
return empUserName;
}
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public void setEmpUserName(String empUserName) {
this.empUserName = empUserName;
}
public String getEmpPassword() {
return empPassword;
}
public void setEmpPassword(String empPassword) {
this.empPassword = empPassword;
}
public String getEmpStatus() {
return empStatus;
}
public void setEmpStatus(String empStatus) {
this.empStatus = empStatus;
}
}
Requirement:
I want to select fields empid, empname, jobtitle from employee_info and field status from employee_login table when the empid matches on both table...
Please help me to complete my work...
Any suggestions and guidance are appreciated...
There is an association between EmployeeInfoForm and EmployeeLoginForm that I am not seeing in your code. Maybe there is an Employee class there? If that is the case then you need to add that. So let us assume that each employee has many forms. Then you will code the Employee side of the relationship like this:
public class Employee{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee")
private Set<EmployeeLoginForm> loginForms = new HashSet<EmployeeLoginForm>();
...
}
And the Many side of the relationship in the EmployeeLoginForm class:
#ManyToOne
Employee employee;
This will create the table structure such that:
emploee = (id, etc ...)
employeelogin = (id, employee, ....)
Now, any time you need a list of the Logins of an Employee you get it from the Employee object without needing a Query.
Set<EmployeeLoginForm> logins = e.getLoginForms(); //where e is an employee object.
If you did want to query you can do
select o from EmployeeLoginForm o join o.employee
But that is unnecessary in this case.
You are thinking in database / pure SQL terms when you talk about performing joins with select statements. The power (and danger) of Hibernate is that it abstracts this away from you and lets you think in Object terms. What you need is a relationship between the 2 objects and then let Hibernate handle this relationship.
I recommend you spend some time reading this:
http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/associations.html
to get a better understanding of how Hibernate can help.
You can do the following using the Hibernate criteria projection:
public List extractEmployeeAttributes() {
log.debug("extractEmployeeAttributes");
try {
Session session = sessionFactory.getCurrentSession();
session.beginTransaction();
Criteria c1 = session.createCriteria(employee_info.class,emp_info);
Criteria c2 = session.createCriteria(employee_login.class,emp_log);
c1.setProjection(Projections.projectionList()
.add(Projections.property("empid"))
.add(Projections.property("empname"))
.add(Projections.property("jobtitle"))
.add(Projections.property("employee_info "))
.add(Restrictions.and(Property.eqName(emp_info.empId,emp_log.empId))
return c1.list();
} catch (RuntimeException re) {
log.error("extractEmployeeAttributes failed", re);
throw re;
}
}

Relationships question in hibernate

I'm learning Hibernate and Play framework (also add Java into account...). I'm having problems saving this kind of entity
#Entity
#Table(name="users")
public class User extends Model {
#Required
public String username;
#Column(name="user_displayname",nullable=true)
public String displayname;
#Password
public String user_password;
#Email
#Column(name="user_email",nullable=false,unique=true)
public String user_email;
public String user_salt;
public Date user_joindate;
#ManyToOne
#JoinTable(name="users_meta")
public UserMeta userdata;
#Required
public boolean user_isActive;
#OneToOne(targetEntity=UserPhotos.class,cascade=CascadeType.ALL)
#JoinColumn(name="id",referencedColumnName="userID")
public UserPhotos userPhoto;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="links_rol2user")
public List<Rol> rol;
public User (String username, models.Pass password, String user_email) {
this.username = username;
this.user_password = password.getHashedPassword();
this.user_salt = password.getUserHash();
this.user_email = user_email;
this.user_joindate = new Date();
this.user_isActive = false;
}
This is my code when I'm registering a user
// check if the validation has errors
if(validation.hasErrors()) {
params.flash(); // add http parameters to the flash scope
validation.keep(); // keep the errors for the next request
register();
} else {
Cache.delete(uuid);
Pass pass = new Pass(password,new Date().toString());
User newUser = new User(firstName, pass, email);
UserMeta utest = new UserMeta(newUser.id);
utest.setUserTownID(pueblos);
newUser.setUserMeta(utest);
newUser.save();
Logger.info("NewUser ID : %s", newUser.getId());
// UserMeta userInfo = new UserMeta(newUser.getId());
// userInfo.setUserTownID(pueblos);
// userInfo.save();
// TODO salvar foto a null
// Confirmation left
Cache.set("thankyou", "alright!", "3mn");
thankyou();
}
I'm trying to save the userMeta, it does creates a new record when I set the userMeta object into newUser (not visible right now), but it doesn't insert the new ID created in newUser.
What kind of relation do I need? before I tweaked the code as it is now, it was a OneToOne relationship, worked quite well, but now when I was completing the register functions it kinda hit me that I needed to save userMeta object too..
If you need more info let me know, I don't know if I explained it well or not, just trying to get the hang of how Hibernate do relations, etc.
Adding UserMeta:
*/
#Entity
#Table(name="users_meta")
public class UserMeta extends Model {
#Lob
#Column(name="userBio")
public String userBio;
#Column(name="userPhotoID",nullable=true)
public Long userPhotoID = null;
#Column(name="userRoleID", nullable=false)
public Long userRoleID = 2L;
#Lob
public String userDescription;
#Column(name="userViews", nullable=false)
public Long userViews = 0L;
#Column(name="userFavoriteCount", nullable=false)
public Long userFavoriteCount = 0L;
#Column(name="userTotalComments", nullable=false)
public Long userTotalComments = 0L;
#Column(name="userTotalUploadedVideos", nullable=false)
public Long userTotalUploadedVideos = 0L;
public Long userTownID;
public Long userID;
public UserMeta() {}
public UserMeta(Long userid) {
this.userBio = "El usuario no ha escrito nada todavia!";
this.userDescription = "El usuario todavia no se ha describido!";
this.userID = userid;
}
public Long getUserTownID() {
return userTownID;
}
public void setUserTownID(Long userTownID) {
this.userTownID = userTownID;
}
}
// pass model
public class Pass {
protected String hashed;
protected String userHash;
public Pass(String passwordToHash, String salt) {
StringBuffer passSalt = new StringBuffer(passwordToHash);
this.userHash = DigestUtils.md5Hex(salt);
passSalt.append(this.userHash);
passSalt.append(Play.configuration.getProperty("application.passwordSalt"));
this.hashed = DigestUtils.sha512Hex(passSalt.toString());
}
public String getHashedPassword() {
return this.hashed;
}
public String getUserHash() {
return this.userHash;
}
}
There seems to be a lot going on there! But from what I can tell, you problem is with the id that you are passing into the UserMeta.
As you are extending Model, the id is being generated by the Model class. However, this is not set until after the entity is saved to the database (as the id is auto-generated by the database).
Therefore, because you are passing the id into the UserMeta before the User object is saved, the value of id will be null.
If you can save the User object before you create your UserMeta object, your code should work.

Categories

Resources