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);
Related
I want to have a relation oneToOne between Person and Address. So my Entities will be (without getter and setter):
public class Address {
private Integer addressId;
private String streetAddress;
...getter and setter
}
public class Person {
private Integer personId;
private String name;
private Integer addressId;
...getter and setter
}
I have a mapper for Person and a mapper for Address:
#Mapper
public interface AddressMapper {
#Insert("Insert into address (streetAddress) values(#{streetAddress})")
#Options(useGeneratedKeys = true, flushCache = FlushCachePolicy.TRUE, keyProperty="addressId")
public Address saveAddress(Address address);
#Select("SELECT * FROM Address")
public List<Address> getAll();
}
#Mapper
public interface PersonMapper {
#Insert("Insert into person(name,addressId) values (#{name},#{addressId})")
public Integer save(Person person);
#Select("select * from Person ")
#MapKey("personId")
Map<Integer, Person> getAllPerson();
}
Then I use a Controller to generete a Person and associate an Address (it is just a simple example):
#GetMapping("/insert")
public Integer insertPerson() {
Person person = new Person("Nome1");
Address address = new Address("via test");
int addressId = addressMapper.saveAddress(address);
person.setAddressId(addressId);
return personMapper.save(person);
}
My problem is that addressMapper.saveAddress(address); return the number of addresses saved not the id. There is a way to make it return the ID of the address inserted using Java Configuration?
Otherwise is it possibile to use a Person entity in this way?
public class Person {
private Integer personId;
private String name;
private Address addressId;
...getter and setter
}
Github code (it is public so you can clone the repository if you want):
https://github.com/Mazzotta13/MyBatisExample
I'm using Neo4j to create graphs. The below codes is an example for spring data Neo4j. I can save a node entity when no id property value is provided.
But how to save a node entiry with a specific id property value?
Model Class:
#Data
#NodeEntity
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
private String title;
#Relationship(type = "ACTED_IN")
private List<Movie> movies = new ArrayList<>();
}
Repository Class
public interface PersonRepository extends Neo4jRepository<Person, Long> {
#Query("MATCH (n:Person {name:{name}}) RETURN n")
List<Person> findByName(#Param("name") String name);
}
Controller Class
#RestController
#RequestMapping("/person")
public class PersonController {
#Autowired
private PersonRepository personRepository;
#PostMapping("/save")
public Map save(#RequestBody Person person) {
Map resultMap = new HashMap();
String code = "200";
String msg = "success";
// It can save success when no id property value is provided
Person savedPerson = personRepository.save(person);
resultMap.put("code", code);
resultMap.put("msg", msg);
resultMap.put("data", savedPerson);
return resultMap;
}
}
I have tried it successfully and can be easily done provide the "id" should be
String not Long
Domain/DAO class:
#Id
#GeneratedValue(strategy = Neo4JCustomIdStrategy.class)
String id;
Repository Class:
#Repository
public interface PersonRepository extends Neo4jRepository<Person, String>{
}
And lastly, custom implementation of Strategy:
public class Neo4JCustomIdStrategy implements IdStrategy {
#Override
public Object generateId(Object entity) {
return String.valueOf(entity.hashCode());
}
}
The library I am using is spring-data-neo4j
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.
I have a Role node that contains some privileges that I am trying to persist into Neo4j. When I construct the object, I see that the privileges exist, but after the save call they disappear.
Here's my Role Node:
#NodeEntity
public class Role {
#GraphId Long id;
private RoleType roleType;
//#RelatedToVia(type="HAS_ROLE", direction=Direction.OUTGOING)
private List<Person> users;
private List<Privilege> defaultPrivileges;
//private List<Task> tasks;
public Role(){}
public Role(RoleType roleType){
this.roleType=roleType;
this.defaultPrivileges=roleType.getDefaultPrivileges();
}
}
Here's my save:
admin= roleRepository.save(admin);
Before I save the object it is fully populate and after it's empty. Any ideas as to why that might be
EDIT:
The code causing the issue is in my Privilege class.
This does not work:
public class Privilege {
private String name;
public Privilege(PrivilegeType pt) {
this.name = pt.name();
}
}
This works:
public class Privilege {
private String name;
public Privilege(String pt) {
this.name = pt;
}
}
Why would that be causing it to not persist? What am I missing in my RoleRepository?
It reloads the entity after storing it, and by default it only loads a shallow copy of related information.
you can use template.fetch(role.users) or template.fetch(role.tasks)
or add #Fetch to the tasks list for instance.
I am implementing a banking application and have three tables in my database (User, Account and AccountActivity):
The implementation of the Account and AccountActivity classes look like this:
#MappedSuperclass public abstract class AbstractDomain implements Serializable {
#Id #GeneratedValue
private long id = NEW_ID;
public static long NEW_ID = -1;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public boolean isNew() {
return id==NEW_ID;
}
}
#Table(name="ACCOUNT_ACTIVITY")
#Entity
public class AccountActivity extends AbstractDomain {
#Column(name="NAME")
private String Name;
#Column(name="XDATE")
private Date Date;
#Column(name="VALUE")
private double Value;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="ACCOUNTID")
private Account ACCOUNT;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="OTHERACCOUNTID")
private Account OTHERACCOUNT;
public String getName() {
return Name;
}
// ...
}
And:
#Table(name="ACCOUNT")
#Entity
public class Account extends AbstractDomain {
#Column(name="NAME")
private String Name;
#Column(name="XDATE")
private Date Date;
#Column(name="VALUE")
private double Value;
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name="USERID")
private User USER;
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<AccountActivity> AccountActivity = new ArrayList<AccountActivity>();
// ...
}
To store new accounts in my database I use this:
public Account storeAccount(Account ac) {
User x = ac.getUser();
x = em.merge(x);
ac = em.merge(ac);
return ac;
}
which works to just store new accounts in my database. I wanted to implement the functionality that when account activity information is added to an already saved account,
that account will be updated and the added information (account activity) is cascaded to the
AccountActivity table using this piece of code:
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<AccountActivity> AccountActivity = new ArrayList<AccountActivity>();
When I test this code I get the error:
java.sql.SQLException: Integrity constraint violation -no parent
FK670B7D019607336A table: ACCOUNT_ACTIVITY in statement
Can anybody help me with this problem?
update
I test with this piece of junit code:
public void testAddAccountActivities() {
User user = dummyPersistedUser();
User user2 = dummyPersistedUser();
Account account = getTestUtils().dummyEmptyAccount(user);
Account account2 = getTestUtils().dummyEmptyAccount(user2);
account=accountManager.storeAccount(account);
account2=accountManager.storeAccount(account2);
getTestUtils().fillAccounts(account, account2);
accountManager.storeAccount(account);
accountManager.storeAccount(account2);
assertEquals(2,accountManager.getAccount4Name(account.getName()).getAccountActivity().size());
assertEquals(2,accountManager.getAccount4Name(account2.getName()).getAccountActivity().size());
}
where fillAccounts(account, account2) just inserts some AccountActivities that should be added to the graph.:
AccountActivity aa = new AccountActivity();
aa.setDate(new Date());
aa.setName("test activity");
aa.setAccount(a1);
aa.setValue(value);
aa.setOtherAccount(a2);
account.addAccountActivity(aa)
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
#JoinColumn(name ="accountid") // you have several references from AccountActivity to Account. You need to specify the join column in this case.
private List<AccountActivity> AccountActivity = new ArrayList<AccountActivity>();
As a first observation, I think you shouldn't have the variable name the same as the class. So, instead of this private List <AccountActivity> AccountActivity, you can write something like private List <AccountActivity> accountActivity.