Spring Data Neo4j Parent Child filtering - java

I'm having a node relationship like above diagram
my Classes
#NodeEntity
public class User
{
#GraphId
private Long id;
#Property(name="name")
private String name;
#Relationship(type = "CAN_ACCESS", direction = Relationship.OUTGOING)
private List<Library> libraries;
// is this necessary ?????
#Relationship(type = "CAN_ACCESS", direction = Relationship.OUTGOING)
private List<Book> books;
public User()
{
}
// getters
// setters
}
#NodeEntity
public class Library
{
#GraphId
private Long id;
#Property(name="name")
private String name;
#Relationship(type = "CONTAINS", direction = Relationship.OUTGOING)
private List<Book> books;
// is this necessary ?????
#Relationship(type = "CAN_ACCESS", direction = Relationship.INCOMING)
private List<User> users;
public Library()
{
}
// getters
// setters
}
#NodeEntity
public class Book
{
#GraphId
private Long id;
#Property(name="name")
private String name;
// is this necessary ?????
#Relationship(type = "CONTAINS", direction = Relationship.INCOMING)
private Library library;
// is this necessary ?????
#Relationship(type = "CAN_ACCESS", direction = Relationship.INCOMING)
private List<User> users;
public Book()
{
}
// getters
// setters
}
I have User node Id = 21 and Library node Id = 32.I want to query Books that belongs to Library 32 but only User 21 can access.
Note - although User 21 "CAN_ACCESS" Library 32, that does not mean he "CAN_ACCESS" all Books "CONTAINS" in Library 32
My current approach in my service class is
#Autowired
private LibraryRepository libraryRepository;
#Autowired
private UserRepository userRepository;
#Autowired
private BookRepository bookRepository;
#Autowired
private Session session;
public void testGraph()
{
Long userId = 21;
Long libId = 32;
int depth = 1;
Library library = libraryRepository.findOne(32,depth);
List<Book> books = library.getBooks();
List<Book> userAccessibleBooks = getUserAccessibleBooks(books,userId);
}
public List<Book> getUserAccessibleBooks(List<Book> books,Long userId)
{
// Loop over book list and get List<User> users;
// check this user "CAN_ACCESS" the book
// return accessible book list
}
I don't think this is the best approach. Assuming you have millions of Books in Library
Any solution ? or am i missing something in Spring data neo4j (Filters) ?
Summary
I want to get Library 32 with filtered Book List as children which User 21 "CAN_ACCESS"

You want to use repository finder for this kinds of queries. You want to return List<Book> so this will go into BookRepository.
Easiest way is to define a cypher query:
MATCH (u:User), (l:Library)
WHERE
ID(u) = {userId} AND ID(u) = {libraryId}
MATCH
(u)-[:CAN_ACCESS]->(b:Book),
(l)-[:CONTAINS]-(b)
RETURN b
Using native graph ids is not really recommended, so if you introduce e.g. uuid the query might look like this:
MATCH
(u:User {uuid:{userUuid}}),
(l:Library {uuid:{libraryUuid}}),
(u)-[:CAN_ACCESS]->(b:Book),
(l)-[:CONTAINS]-(b)
RETURN b
Then add a finder method with this query into BookRepository interface:
#Query("MATCH
(u:User {uuid:{userUuid}}),
(l:Library {uuid:{libraryUuid}}),
(u)-[:CAN_ACCESS]->(b:Book),
(l)-[:CONTAINS]-(b)
RETURN b")
List<Book> findBooksByUserAndLibrary(#Param("userUuid") String userUuid, #Param("libraryUuid) String libraryUuid);

Related

How do I get MongoDB ChangeStreamDocument out as my dto instead of Document type?

I have the following class in my MongoDB project:
#Document(collection = "teams")
public class Team {
private #MongoId(FieldType.OBJECT_ID)
#Schema(type = "string", example = "60b0c56e4192f01e8745bd75")
ObjectId id;
#Schema(example = "56373")
private Integer orgId;
private String name;
private List<Member> players;
private List<Member> staff;
public class Member{
private ObjectId id;
private String name;
}
}
As you can see, this class represents the documents in my teams collection in MongoDB.
I'm trying to create a change stream, because I want to monitorize players and staff players joining and leaving teams, as well as existing teams being deleted from the teams collection.
So this is what I've tried:
#Component
public class MongoChangeStream {
private final MongoTemplate mongoTemplate;
public MongoDBChangeStream(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#EventListener(ContextRefreshedEvent.class)
public void changeStream() {
// Select the collection to query
MongoCollection<Document> collection = mongoTemplate.getCollection("teams");
// Create pipeline for operationType filter
List<Bson> pipeline = Arrays.asList(
Aggregates.match(
Filters.in("operationType",
Arrays.asList("insert", "update", "delete"))));
// Create the Change Stream and watch on the filters in the pipeline
ChangeStreamIterable<Document> changeStream = collection.watch()
.fullDocument(FullDocument.UPDATE_LOOKUP)
.fullDocumentBeforeChange(FullDocumentBeforeChange.REQUIRED);
// Iterate over the Change Stream
for (ChangeStreamDocument<Document> changeEvent : changeStream) {
switch (changeEvent.getOperationType().name()) {
case "UPDATE":
if (changeEvent.getUpdateDescription().getUpdatedFields().containsKey("players")) {
// Do something
}
if (changeEvent.getUpdateDescription().getUpdatedFields().containsKey("staff")) {
// Do something
}
break;
case "DELETE":
// Do something
break;
}
}
}
}
My question is basically how do I get the documents out from the changeStream as Team objects instead of Document objects?
I've tried to change all the occurrences of Documents with Team, but then I get this error:
Can't find a codec for CodecCacheKey{clazz=class com.test.dto.Team, types=null}.
I ended up making it work like this:
#Document(collection = "teams")
public class Team {
#Id
#BsonProperty("_id")
private ObjectId id;
private Integer orgId;
private String name;
private List<Member> players;
private List<Member> staff;
public class Member {
#Id
private ObjectId id;
private String name;
}
}
Then I added CodecRegistry to my MongoChangeStream class like this:
So this is what I've tried:
#Component
public class MongoChangeStream {
private final MongoTemplate mongoTemplate;
public MongoDBChangeStream(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#EventListener(ContextRefreshedEvent.class)
public void changeStream() {
// Select the collection to query
CodecRegistry pojoCodecRegistry = org.bson.codecs.configuration.CodecRegistries.fromRegistries(MongoClientSettings.getDefaultCodecRegistry(), org.bson.codecs.configuration.CodecRegistries.fromProviders(PojoCodecProvider.builder().conventions(List.of(ANNOTATION_CONVENTION)).automatic(true).build()));
MongoCollection<Team> collection = mongoTemplate.getCollection("teams").withCodecRegistry(pojoCodecRegistry).withDocumentClass(Group.class);
// Create pipeline for operationType filter
List<Bson> pipeline = Arrays.asList(
Aggregates.match(
Filters.in("operationType",
Arrays.asList("insert", "update", "delete"))));
// Create the Change Stream and watch on the filters in the pipeline
ChangeStreamIterable<Team> changeStream = collection.watch()
.fullDocument(FullDocument.UPDATE_LOOKUP)
.fullDocumentBeforeChange(FullDocumentBeforeChange.REQUIRED);
// Iterate over the Change Stream
for (ChangeStreamDocument<Team> changeEvent : changeStream) {
switch (changeEvent.getOperationType().name()) {
case "UPDATE":
if (changeEvent.getUpdateDescription().getUpdatedFields().containsKey("players")) {
// Do something
}
if (changeEvent.getUpdateDescription().getUpdatedFields().containsKey("staff")) {
// Do something
}
break;
case "DELETE":
// Do something
break;
}
}
}
}

How work with immutable object in mongodb and lombook without #BsonDiscriminator

I tried to work with immutable objects in MongoDB and Lombok. I found a solution to my problem but it needs to write additional code from docs but I need to used Bson annotations and create a constructor where describes fields via annotations. But if I user #AllArgsConstructor catch exception: "Cannot find a public constructor for 'User'" because I can't use default constructor with final fields. I think i can customize CodecRegistry correctly and the example will work correctly but I couldn't find solution for it in docs and google and Stackoverflow.
Is there a way to solve this problem?
#Data
#Builder(builderClassName = "Builder")
#Value
#BsonDiscriminator
public class User {
private final ObjectId id;
private final String name;
private final String pass;
private final String login;
private final Role role;
#BsonCreator
public User(#BsonProperty("id") final ObjectId id,
#BsonProperty("name") final String name,
#BsonProperty("pass") final String pass,
#BsonProperty("login") final String login,
#BsonProperty("role") final Role role) {
this.id = id;
this.name = name;
this.pass = pass;
this.login = login;
this.role = role;
}
#AllArgsConstructor
public enum Role {
USER("USER"),
ADMIN("ADMIN"),
GUEST("GUEST");
#Getter
private String value;
}
public static class Builder {
}
}
Example for MongoDB where I create, save and then update users:
public class ExampleMongoDB {
public static void main(String[] args) {
final MongoClient mongoClient = MongoClients.create();
final MongoDatabase database = mongoClient.getDatabase("db");
database.drop();
final CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
final MongoCollection<User> users = database.getCollection("users", User.class).withCodecRegistry(pojoCodecRegistry);
users.insertMany(new ExampleMongoDB().getRandomUsers());
System.out.println("Before updating:");
users.find(new Document("role", "ADMIN")).iterator().forEachRemaining(
System.out::println
);
System.out.println("After updating:");
users.updateMany(eq("role", "ADMIN"), set("role", "GUEST"));
users.find(new Document("role", "GUEST")).iterator().forEachRemaining(
System.out::println
);
}
public List<User> getRandomUsers() {
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 15; i++) {
users.add(
User.builder()
.login("log" + i)
.name("name" + i)
.pass("pass" + i)
.role(
(i % 2 == 0) ? User.Role.ADMIN : User.Role.USER
).build()
);
}
return users;
}
}
This should work (it worked for me):
#Builder(builderClassName = "Builder")
#Value
#AllArgsConstructor(onConstructor = #__(#BsonCreator))
#BsonDiscriminator
public class User {
#BsonId
private final ObjectId _id;
#BsonProperty("name")
private final String name;
#BsonProperty("pass")
private final String pass;
#BsonProperty("login")
private final String login;
#BsonProperty("role")
private final Role role;
}
Then in lombok.config add these (in your module/project directory):
lombok.addLombokGeneratedAnnotation=true
lombok.anyConstructor.addConstructorProperties=true
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonProperty
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonId
Also piece of advice, keep _id if you are going to use automatic conversion to POJOs using PojoCodec, which will save a lot of trouble.

Hot to add a list of parameters of another object in jpa Criteria

I have a class Director
public class Director {
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="transacao_id")
private Company company;
private Date registrationDate;
...
}
I have a class Company
public class Company {
private Long id;
private String cnpj;
private String description;
...
}
I need to add another filter in predicates...
I need to fetch a list from Director, filtering through a list of cnpj using jpa predicates, for example:
private Predicate[] criarRestricoes(FilterDTO filter, CriteriaBuilder builder, Root<Director> root) {
List<Predicate> predicates = new ArrayList<>();
Date today = new Date();
if (filter.getRegistrationDate() != null) {
predicates.add(builder.between(root.get("registrationDate").as(Date.class), today, filter.dateParam));
}
if (!StringUtils.isEmpty(filter.getCnpj())) {
predicates.add(builder.equal(???????????, ???????????????);
}
return predicates.toArray(new Predicate[predicates.size()]);
}
builder.equal(root.join("company").get("cnpj"), filter.getCnpj());

Spring HATEOAS with nested resources and JsonView filtering

I am trying to add HATEOAS links with Resource<>, while also filtering with #JsonView. However, I don't know how to add the links to nested objects.
In the project on on Github, I've expanded on this project (adding in the open pull request to make it work without nested resources), adding the "Character" entity which has a nested User.
When accessing the ~/characters/resource-filtered route, it is expected that the nested User "player" appear with the firstNm and bioDetails fields, and with Spring generated links to itself, but without the userId and lastNm fields.
I have the filtering working correctly, but I cannot find an example of nested resources which fits with the ResourceAssembler paradigm. It appears to be necessary to use a ResourceAssembler to make #JsonView work.
Any help reconciling these two concepts would be appreciated. If you can crack it entirely, consider sending me a pull request.
User.java
//package and imports
...
public class User implements Serializable {
#JsonView(UserView.Detail.class)
private Long userId;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String bioDetails;
#JsonView({ UserView.Summary.class, CharacterView.Summary.class })
private String firstNm;
#JsonView({ UserView.Detail.class, CharacterView.Detail.class })
private String lastNm;
public User(Long userId, String firstNm, String lastNm) {
this.userId = userId;
this.firstNm = firstNm;
this.lastNm = lastNm;
}
public User(Long userId) {
this.userId = userId;
}
...
// getters and setters
...
}
CharacterModel.java
//package and imports
...
#Entity
public class CharacterModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(CharacterView.Summary.class)
private Long characterId;
#JsonView(CharacterView.Detail.class)
private String biography;
#JsonView(CharacterView.Summary.class)
private String name;
#JsonView(CharacterView.Summary.class)
private User player;
public CharacterModel(Long characterId, String name, String biography, User player) {
this.characterId = characterId;
this.name = name;
this.biography = biography;
this.player = player;
}
public CharacterModel(Long characterId) {
this.characterId = characterId;
}
...
// getters and setters
...
}
CharacterController.java
//package and imports
...
#RestController
#RequestMapping("/characters")
public class CharacterController {
#Autowired
private CharacterResourceAssembler characterResourceAssembler;
...
#JsonView(CharacterView.Summary.class)
#RequestMapping(value = "/resource-filtered", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseStatus(HttpStatus.OK)
public Resource<CharacterModel> getFilteredCharacterWithResource() {
CharacterModel model = new CharacterModel(1L, "TEST NAME", "TEST BIOGRAPHY", new User(1L, "Fred", "Flintstone"));
return characterResourceAssembler.toResource(model);
}
...
}
CharacterResourceAssembler.java
//package and imports
...
#Component
public class CharacterResourceAssembler implements ResourceAssembler<CharacterModel, Resource<CharacterModel>>{
#Override
public Resource<CharacterModel> toResource(CharacterModel user) {
Resource<CharacterModel> resource = new Resource<CharacterModel>(user);
resource.add(linkTo(CharacterController.class).withSelfRel());
return resource;
}
}

How to access Object.field in criteria in Hibernate

i have write the criteria for company class.
below are company class, companySearch class and criteria. But criteria list is throw exception. exception is "org.hibernate.QueryException: could not resolve property: san.san of: com.sesami.common.domain.Company". How to access Company.san.san?
Company class
public class Company extends DomainObject implements UserDetails {
private Long id;
private String companyName;
private CompanyType companyType;
private String description;
private String companyURL;
private String billToEmail;
private String hashPassword;
private SAN san;
#OneToOne(cascade = { CascadeType.ALL })
public SAN getSan() {
return san;
}
public void setSan(SAN san) {
this.san = san;
}
...
}
CompanySearch
public class CompanySearch {
private String companyName;
private String email;
private Long san;
private String gstNumber;
......
public Long getSan() {
return san;
}
public void setSan(Long san) {
this.san = san;
}
...
}
Criteria
companyCriteria = this.getSession().createCriteria(
Company.class);
if (companySearch.getSan() != null
&& !"".equals(companySearch.getSan()))
companyCriteria.add(Restrictions.eq("san.san",
companySearch.getSan()));
Integer count = ((Long) companyCriteria.setProjection(
Projections.rowCount()).uniqueResult()).intValue();
companyCriteria.setProjection(null);
companyCriteria.setResultTransformer(Criteria.ROOT_ENTITY);
companyCriteria
.setFirstResult((pager.getPage() - 1) * pager.getPageSize())
.setMaxResults(pager.getPageSize()).list();
List<Company> companies = companyCriteria.list();
PagedResultSet pr = new PagedResultSet();
pr.setPager(pager);
pr.setResultSet(companies);
pr.setRowCount(count);
return pr;
You must create a join to the San entity, using a subcriteria, or an alias:
companyCriteria.createAlias("san", "sanAlias");
companyCriteria.add(Restrictions.eq("sanAlias.san",
companySearch.getSan()));
or
companyCriteria.createCriteria("san").add(Restrictions.eq("san",
companySearch.getSan()));
This is well explained in the Hibernate reference documentation and even in the Criteria javadoc.
Note that this has absolutely nothing to do with Spring, and everything to do with Hibernate. If you searched in the Spring doc for how to do this, no wonder you didn't find anything.

Categories