I am new to Neo4J and working with Spring data repository. Following is the domain definition
#NodeEntity
public class Actor {
Long id;
private Set<Role> roles;
}
#RelationshipEntity(type="PLAYED_IN")
public class Role {
#GraphId private Long relationshipId;
#Property private String title;
#StartNode private Actor actor;
#EndNode private Movie movie;
}
#NodeEntity
public class Movie {
private Long id;
private String title;
}
And have GraphRepository defined for each entity class
Following code does not save the RelationshipEntity
Actor actor = new Actor("actorName");
actor = actorRepository.save(actor);
Movie movie = new Movie("movieTitle");
movie = movieRepository.save(movie);
Role role = new Role(actor, movie, "roleTitle");
role = roleRepository.save(role);
Do I have to annotate the roles variable in Actor class?
Do I have to populate roles collection before saving Actor? If I do so then the properties on Role are not saved.
Yes, you must annotate the roles in the Actor entity.
If you're using neo4j-ogm 1.1.3 or an earlier version, make sure that when you create the new role, you add this to the collection of roles in the Actor entity.
If you're using neo4j-ogm 1.1.4-SNAPSHOT, your code should work (after you annotate the roles)
Related
Let's say I have two entities:
#Entity
public class Phone {
#Id
private Long id;
private String number;
}
#Entity
public class Person {
#Id
private Long id;
private String name;
}
The relationship between a person and a phone is one to one.
How could I access only the phone's number in the Person entity mapped by the phone's id
#Entity
public class Person {
#Id
private Long id;
private String name;
// ???
private String phoneNumber;
}
The reason for not mapping the whole entity is because in some more realistic entities there are too many properties.
I don't think you can, but something like this might be acceptable:
public class Person {
#OneToOne
#JoinColumn(name = "phone_id")
private Phone phone;
public String getPhoneNumber() {
return phone.getNumber();
}
}
Although you have mapped the whole object, not just the single property, you have only exposed the single property you want. The other stuff is hidden.
Alternatively, do it at the DB layer using a View:
create view person_with_phone as
select p.id, p.name,f.number
from person p
join phone f on f.id=p.phone_id
and then have an entity class to match the view. You'll need to turn off schema creation in your JPA implementation.
I am working on a Restful service built with Java Spring and I have some issues modeling the data. I want to store shelfs with books. The books belong to a given category. I have a POST request to store shelfs to a mysql database (via service and CrudRepository). However I am not able to store more than one book of the same category. Here are my (simplified) entities.
A Shelf with an id and a collection of books.
#Entity
public class Shelf{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "shelf")
private List<Book> books= new ArrayList<>();
...
}
The class Book is defined as follows:
#Entity
public class Book{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "category_id")
private Category category;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonIgnore
private Shelf shelf;
Each book belongs to a category(e.g. thriller, fiction, etc.). Here is the category entity:
#Entity
public class Category {
private Long id;
private String name;
And finally my Controller:
#RestController
public class ShelfController {
#Autowired
private ShelfService shelfService;
#PostMapping("/shelfs")
public Shelf addShelf(#RequestBody Shelf shelf) {
return shelfService.addShelf(shelf);
}
Now here is my problem: The categories will be given and there will be no option to change these, I would therefore like to have them stored in the database or hard code them as static objects. In the Post request for new shelfs I would like to provide only the category id and make the controller find the corresponding object itself.
What I did so far was to treat the categories as a usual Entity, so whenever I added a new shelf with books having a category_id, the category was created with the given id and an empty name. But as soon as I used the same category id again, the application threw a com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '1' for key 'PRIMARY' exception. I don't want the controller to create new category objects, but instead want it to fetch the corresponding objects from a service or a static Collection.
So my question is: How can I achieve this?
Hints for solutions/tricks to improve the design are most welcome, I am new to the topic.
One solution is to create Data Transfer Objects (DTO). Example:
class ShelfDTO {
private Long id;
private List<Long> bookIds;
}
Then use this class to receive the POST requests:
#RestController
public class ShelfController {
#Autowired private ShelfService shelfService;
#PostMapping("/shelfs")
public Shelf addShelf(#RequestBody ShelfDTO shelfDto) {
return shelfService.addShelf(shelfDto);
}
}
Then modify your ShelfService to convert the DTO to an Entity:
#Service
public class ShelfService {
#Autowired private ShelfRepository shelfRepository;
#Autowired private BookRepository bookRepository;
#Transactional
public Shelf addShelf(ShelfDTO shelfDto) {
List<Book> books = bookRepository.findAllById(shelfDto.getBookIds());
return shelfService.addShelf(new Shelf(books));
}
}
Final comment: I noticed that you have a bidirectional relationship. You are responsible for keeping it in a consistent state.
The easiest way is to create the methods addTo(shelf, book) and removeFrom(shelf, book) that encapsulate the logic of both adding the book to the list in the shelf and setting the shelf in the book.
I'm building an application with Google App Engine. I'm using Objectify for Datastore persistence. I have two POJO class.
#Entity
public class Book
{
#Id private Long id;
private String name;
private String isbn;
private String author;
}
and
#Entity
public class Person {
#Id private String email;
private String password;
private String name;
private List<Book> myBooks;
}
A person can contain many books but book only belongs to one person. Currently I'm saving data like this.
//data from Front-end
Person p = new Person(email, password, name);
PersonDAO dao = new PersonDAO();
dao.save(p);
//...
//data from Front-end
Book b = new Book(name, author, isbn);
BookDAO daoBook = new BookDAO();
daoBook.save(b);
//...
Person q = dao.load(email);
q.addBook(b);
dao.save(q);
ObjectifyService ofy() with methods are implemented in DAO classes.
It's ok my implementation? How can I optimize the relationship? Every time that I need create a book this is saved like Entity but I need the relationship with a person, therefore I'm saving book twice. I've seen implementantions with Key, Ref #Load tags but I don't know how are working them.
Besides, Person POJO has a Date field. will It be saved normally?
It has being quite a while since I wrote this code, but here it goes:
#Entity
public class Book {
#Id
private Long id;
#Parent
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
private Key<User> user;
private String author;
private String isbn;
private String name;
}
Take a look at Udacity Conference Central for full example. It is the final project of Developing Scalable Apps in Java
with Google App Engine, by Udacity.
I am developing an app where the user can have one or more roles, for this I decided to created a mapping (intermediate) table, so I ended with User, Role and UserRole like this:
In this app the role(s) a user has determines wheneaver he can access certain views or actions in the frontend. The only thing I need is to retrive the roles a user has and add/delete them. JPA Tools created he following EJB for me (simplified):
USER
/**
* The persistent class for the usuario database table.
*
*/
#Entity
#NamedQuery(name="Usuario.findAll", query="SELECT u FROM Usuario u")
public class Usuario implements Serializable {
private static final long serialVersionUID = 1L;
private int idUsuario;
private List<RolUsuario> rolUsuarios;
public Usuario() {
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public int getIdUsuario() {
return this.idUsuario;
}
public void setIdUsuario(int idUsuario) {
this.idUsuario = idUsuario;
}
//bi-directional many-to-one association to RolUsuario
#OneToMany(mappedBy="usuario")
public List<RolUsuario> getRolUsuarios() {
return this.rolUsuarios;
}
public void setRolUsuarios(List<RolUsuario> rolUsuarios) {
this.rolUsuarios = rolUsuarios;
}
public RolUsuario addRolUsuario(RolUsuario rolUsuario) {
getRolUsuarios().add(rolUsuario);
rolUsuario.setUsuario(this);
return rolUsuario;
}
public RolUsuario removeRolUsuario(RolUsuario rolUsuario) {
getRolUsuarios().remove(rolUsuario);
rolUsuario.setUsuario(null);
return rolUsuario;
}
}
USER_ROLE
/**
* The persistent class for the rol_usuario database table.
*
*/
#Entity
#Table(name="rol_usuario")
#NamedQuery(name="RolUsuario.findAll", query="SELECT r FROM RolUsuario r")
public class RolUsuario implements Serializable {
private static final long serialVersionUID = 1L;
private int idRol_Usuario;
private Usuario usuario;
private Rol rol;
public RolUsuario() {
}
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public int getIdRol_Usuario() {
return this.idRol_Usuario;
}
public void setIdRol_Usuario(int idRol_Usuario) {
this.idRol_Usuario = idRol_Usuario;
}
//bi-directional many-to-one association to Usuario
#ManyToOne(fetch=FetchType.LAZY)
public Usuario getUsuario() {
return this.usuario;
}
public void setUsuario(Usuario usuario) {
this.usuario = usuario;
}
//bi-directional many-to-one association to Rol
#ManyToOne(fetch=FetchType.LAZY)
public Rol getRol() {
return this.rol;
}
public void setRol(Rol rol) {
this.rol = rol;
}
}
In my project I am using the EJB to create POJO for the frontend. When I ask for the full list of roles for a given user how should I go about doing this:
Create a UserRole repository using CrudRepository with a metod like
List<RolUsuario> findByUsuario(Usuario user);
Return the list of UserRole to my User Service and go over the
the list extracting each Role into a UserPOJO
Send to frontend.
Or is there any way to just get right off the bat a list of Roles in table UserRole where User(Id?) = something?
This is hard to describe for me. My app only cares for the roles of a user, not the mapping table entity, so the botton line is that somehow I have to get them but I don't know where to start.
Any pointers would be extremely useful.
Edit:
Or I could...
Create UserRole for new role addition to a user.
Adding UserRole to the List inside User.
To get the roles of a user get the UserRolelist instead.
Your schema for User and Role is not commonly used. I advice to you make a #ManyToMany association from a user to roles. If you will need to map a join table to the entity (unlikely) you can do it later. And, please, use English identifiers and Java naming convention (idRol_Usuario). It will help you and others.
#Entity
#Table
public class User {
#Id
#GeneratedValue
private Long pid;
#ManyToMany(fetch = FetchType.LAZY)
private List<Role> roles;
}
#Entity
#Table
public class Role {
#Id
private Long pid;
#Column
private String name;
}
You can use Set<Role> too
#ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
The roles of a User and in the rolUsuarios list. In your User service, look up a user, often with the id. If you have the idUsuario, then user EntityManger.find(Usuario.class, idUsuario) and you can read the user roles by getRolUsuarios, or at least that would be the typical way to do it.
In your table design you have a id for the user_role table (iduserrole), which is not typical for a join table. Typically, you just create your entity with a OneToMany annotation and the join table is created for you:
#Entity
public class User {
#Id #GeneratedValue private Long id;
#ManyToMany
Set<Role> roles;
}
and
#Entity
public class Role {
#Id #GeneratedValue private Long id;
}
These two classes would create three tables, the User table, the Role table, and the User_Role Join Table. The join table would have the each id from the user and the role tables in it, and nothing more.
EDIT: The roles is changed to ManyToMany because otherwise a constraint will be added to the database that will prevent the a Role from being added to more than one user. Typically the role table has only unique roles in it, e.g., USER, ADMIN, etc., and so you want to be able to assign them to more than one user.
Is this what you are looking for?
In SDN4 I wish to persist a #RelationshipEntity which is not a #NodeEntity's property.
Example:
#NodeEntity
public class User{
Long id;
}
#RelationshipEntity(type="FOLLOWS")
public class Follows{
#GraphId private Long relationshipId;
#StartNode private User follower;
#EndNode private User followee;
#Property private Date from;
public Follows(){}
public Follows(User u1, User u2){
this.follower = u1;
this.followee = u2;
}
}
#Repository
interface FollowsRepository extends GraphRepository<Follows>{}
And then persist the Follows #Relationship like this
...
followsRepository.save(new Follows(user1, user2));
...
But when doing so, the Relationship is not persisted!!
Sadly as stated in the accepted answer this cannot (yet) be done (SDN 4.0.0.RELEASE)
Workaround 1
It is possible to persist #RelationshipEntities using #Query in GraphRepositories.
#Query("Match (a:User), (b:User) WHERE id(a) = {0}
AND id(b) = {1} CREATE (a)-[r:FOLLOWS {date:{2}}]->(b) RETURN r ")
Workaround 2
This can also be done by treating Follows as a #NodeEntity, which might not be the most performant thing to do BUT will not affect any of the #NodeEntities of the domain, nor the service layer AND you won't have to mess with the depth factor when loading and persisting entities
#NodeEntity
public class User{
Long id;
}
#NodeEntity
public class Follows{
private Long Id;
#Relationship(type="FOLLOWER")
private User follower;
#Relationship(type="FOLLOWEE")
private User followee;
private Date from;
public Follows(){}
public Follows(User u1, User u2){
this.follower = u1;
this.followee = u2;
}
}
....
//Placed in userService
Follows createFollowsRelationship(User u1, User u2){
return followsRepository.save(new Follows(user1, user2));
}
.....
At the moment, you cannot persist a relationship entity directly when it is not referenced from participating node entities.
You'll have to save the start node and make sure it has a reference to the relationship entity.
There will be some enhancements around how relationship entities are persisted but not in the next release.