Neo4j OGM not saving RelationshipEntity - java

I have read every single question on SO but I still can't figure out why OGM for Neo4j doesn't save my RelationshipEntities.
Here PubmedDocumentNode and PubmedAuthorNode are both saved, but not the relationships citations and authors. MeshHeadingsUI edges instead are created fine.
Code:
#NodeEntity
public class PubmedDocumentNode extends Node {
#Id
#Index(unique = true)
public int PMID;
...
#Relationship(type = "CITES")
public List<PubmedCitesRelationship> citations;
#Relationship(type = "MeSHClass")
public List<MeSHClass> MeshHeadingsUI;
#Relationship(type = "AUTHOR")
public List<PubmedDocAuthorRelationship> authors;
public PubmedDocumentNode() {
node_type = NODE_TYPE;
citations = new ArrayList<>();
MeshHeadingsUI = new ArrayList<>();
authors = new ArrayList<>();
}
}
#NodeEntity
public class PubmedAuthorNode extends Node {
...
#Id
#Index(unique = true)
public String Author_Name;
....
}
#RelationshipEntity(type = "AUTHOR")
public class PubmedDocAuthorRelationship extends FeatureEdge {
public PubmedDocAuthorRelationship() {
label = new float[]{3.0f}; //just to try
}
#Id
#GeneratedValue
public long Id;
#StartNode
public PubmedDocumentNode document;
#EndNode
public PubmedAuthorNode author;
}
Parent classes:
public abstract class FeatureEdge extends DistributedNeo4jEntity {
#Property
public float label[];
public FeatureEdge() {
}
}
public abstract class Node extends DistributedNeo4jEntity {
...
public float state[];
public float label[];
...
public Node() {
...
}
}
public abstract class DistributedNeo4jEntity extends Neo4jEntity {
...
}
public abstract class Neo4jEntity {
#Labels
public List<String> labels = new ArrayList<>(); //I already tried to comment this but no luck
}
Save code:
//s is obtained by openSession
Transaction tx = s.beginTransaction();
arts.forEach(x -> s.save(x));
//arts.forEach(x -> x.authors.forEach(y -> s.save(y))); //tried also
//arts.forEach(x -> x.citations.forEach(y -> s.save(y))); //tried also
tx.commit();
tx.close();

Related

Vaadin Grid - data from multiple objects

I would like to show data from two different objects in one Grid. I have "folders" and "items" and I would like to see them together in one Grid. Folders at the top and items below folders. Something like list view in file manager application. But I don't know how to get the data together. I could probably create an abstract parent class for Item and Folder classes with getter methods, which I'm using in the grid. Is there any better solution for that?
Expected result:
#Route(value = "")
#PageTitle("Items | Test")
public class ListView extends VerticalLayout {
Grid<Item> grid = new Grid<>(Item.class);
ItemService service;
Folder currentFolder;
public ListView(ItemService service) {
this.service = service;
this.currentFolder = service.getAllFolders().get(0);
addClassName("list-view");
add(getGrid());
updateList();
}
private HorizontalLayout getGrid() {
HorizontalLayout layout = new HorizontalLayout(grid);
layout.setSizeFull();
grid.addClassNames("grid");
grid.setColumns("name");
grid.addColumn(testCase -> testCase.getStatus().getValue()).setHeader("Status").setSortable(true);
grid.addColumn(new ComponentRenderer<>(
testCase -> {
Checkbox checkbox = new Checkbox();
checkbox.setValue(testCase.getBooleanValue());
checkbox.setReadOnly(true);
return checkbox;
}
)
).setHeader("Boolean");
grid.getColumns().forEach(col -> col.setAutoWidth(true));
return layout;
}
private void updateList() {
grid.setItems(service.getItemsFromFolder(currentFolder));
}
}
Service:
#Service
public class ItemService {
private final ItemRepository itemRepository;
private final FolderRepository folderRepository;
public ItemService(ItemRepository itemRepository, FolderRepository folderRepository) {
this.itemRepository = itemRepository;
this.folderRepository = folderRepository;
}
public List<Folder> getAllFolders() {
return folderRepository.findAll();
}
public List<Item> getItemsFromFolder(Folder folder) {
return itemRepository.getItemsFromFolder(folder.getId());
}
}
Item Repository:
public interface ItemRepository extends JpaRepository<Item, Long> {
#Query("Select i from Item i where i.folder.id = :folderId")
List<Item> getItemsFromFolder(#Param("folderId") Long folderId);
}
Folder Repository:
public interface FolderRepository extends JpaRepository<Folder, Long> {
}
Item Entity:
#Entity
#Getter
#Setter
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String name = "";
#Enumerated
#Column(columnDefinition = "smallint")
private Status status;
#NotNull
private Boolean booleanValue;
#ManyToOne
#JoinColumn(name = "folder_id")
#NotNull
private Folder folder;
}
Folder Entity:
#Entity
#Getter
#Setter
public class Folder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long parentFolderId;
}
Status enum:
#AllArgsConstructor
public enum Status {
DRAFT("Draft"),
READY("Ready"),
OBSOLETE("Obsolete");
#Getter
private final String value;
}
Grid can only have one type of bean, so an abstract parent class or interface is the way to go. But given the description of your use case of "folders" and "items", have you considered using TreeGrid instead?

Strongly Typed ID Autogeneratation (#EmbeddedId & #GenerateValue)

Im am mightly confused right now. What i am trying to do seems very simple. I have one Entity called 'Order' with a member 'OrderId'. The OrderId has only one property which is a long. this value is what i am trying to generate using #GeneratedValue.
As a database i am using h2 right now. Whenever i try to store something i receive the following error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class xyz.Order
It's not obvous to me what i am doing wrong.
Order.java:
#Getter
#ToString
#EqualsAndHashCode
#NoArgsConstructor(access = PROTECTED)
#Entity(name = "ORDER_TABLE")
public class Order {
#EmbeddedId
private OrderNr orderNr;
#Column(name = "STATE")
private OrderState orderState;
#Embedded
private OtoId otoId;
#Column(name = "customer_wish_date")
private LocalDate customerWishDate;
private Order(Builder builder) {
checkArgument(nonNull(builder.otoId), "OTO ID must not be null");
checkArgument(nonNull(builder.customerWishDate), "CustomerWishDate must not be null");
checkArgument(builder.customerWishDate.isAfter(LocalDate.now()) || builder.customerWishDate.isEqual(LocalDate.now()), "CustomerWishDate must be today or in the future");
this.otoId = builder.otoId;
this.customerWishDate = builder.customerWishDate;
this.orderState = OrderState.ENTERED;
}
private void changeState(OrderState newState, OrderStateChangeEventPublisher changeEventPublisher) {
OrderState previousState = this.orderState;
this.orderState = newState;
changeEventPublisher.publish(this, new OrderStateTransition(previousState, this.orderState));
}
public void hold(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.holding(), changeEventPublisher);
}
public void startFulfillment(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.entered(), changeEventPublisher);
}
public void cancel(OrderStateChangeEventPublisher changeEventPublisher) {
changeState(orderState.canceled(), changeEventPublisher);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
public Builder() {
}
public Order build() {
return new Order(this);
}
private OtoId otoId;
private LocalDate customerWishDate;
public Builder otoId(OtoId otoId) {
this.otoId = otoId;
return this;
}
public Builder customerWishDate(LocalDate customerWishDate) {
this.customerWishDate = customerWishDate;
return this;
}
}
}
OrderNr.java:
#NoArgsConstructor(access = PROTECTED)
#EqualsAndHashCode
#Getter
#ToString
#Embeddable
public class OrderNr implements Serializable {
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ORDER_NR")
private long value;
public OrderNr(Long value) {
checkArgument(nonNull(value), "OrderNr value must not be null");
this.value = value;
}
}

MongoDB repository save() do the insert instead of update

I am writing a PUT request API with spring and mongodb. But the save() inserts a new object instead of update the current one.
#Document("Test")
public class Expense {
#Field(name = "name")
private String expenseName;
#Field(name = "category")
private ExpenseCategory expenseCategory;
#Field(name = "amount")
private BigDecimal expenseAmount;
public Expense( String expenseName, ExpenseCategory expenseCategory, BigDecimal expenseAmount) {
this.expenseName = expenseName;
this.expenseCategory = expenseCategory;
this.expenseAmount = expenseAmount;
}
public String getExpenseName() {
return expenseName;
}
public void setExpenseName(String expenseName) {
this.expenseName = expenseName;
}
public ExpenseCategory getExpenseCategory() {
return expenseCategory;
}
public void setExpenseCategory(ExpenseCategory expenseCategory) {
this.expenseCategory = expenseCategory;
}
public BigDecimal getExpenseAmount() {
return expenseAmount;
}
public void setExpenseAmount(BigDecimal expenseAmount) {
this.expenseAmount = expenseAmount;
}
}
This is my reporsitory class
public interface ExpenseRepository extends MongoRepository<Expense, String> {
}
This is my Service class which shows how to update the class.
#Service
public class ExpenseService {
private final ExpenseRepository expenseRepository;
public ExpenseService(ExpenseRepository expenseRepository) {
this.expenseRepository = expenseRepository;
}
public void updateExpense(String id, Expense expense){
Expense savedExpense = expenseRepository.findById(id)
.orElseThrow(() -> new RuntimeException(
String.format("Cannot Find Expense by ID %s", id)));
savedExpense.setExpenseName(expense.getExpenseName());
savedExpense.setExpenseAmount(expense.getExpenseAmount());
savedExpense.setExpenseCategory(expense.getExpenseCategory());
expenseRepository.save(savedExpense);
}
}
This is my controller
#RestController
#RequestMapping("/api/expense")
public class ExpenseController {
private final ExpenseService expenseService;
public ExpenseController(ExpenseService expenseService) {
this.expenseService = expenseService;
}
#PutMapping("/{id}")
public ResponseEntity<Object> updateExpense(#PathVariable String id, #RequestBody Expense expense){
expenseService.updateExpense(id, expense);
return ResponseEntity.ok().build();
}
}
As shown in mongodb compass, mongodb auto generates an _id field for every object. So I do not define a id field or use #id annotation to define a primary for the collection. However, in the service class, expenseRepository.findById(id) retrieves the desired object and update it. Why does save() do the insert instead of update? Many thanks.
JPA Can't find the existing entry as no id field id set. You need to add an id field and set generation type to auto.
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;

Trigger #OneToMany lazy fetch

I have an ExampleRequest entity that can optionally have one or more ExampleRequestYear. It's currently configured this way (unrelated fields and gettters/setters omitted for brevity, please let me know if you need anything else):
#Entity
#Table(name = "EXAMPLE_REQUEST")
#SequenceGenerator(name = "EXAMPLE_REQUEST_ID_SEQ", sequenceName = "EXAMPLE_REQUEST_ID_SEQ", allocationSize = 1)
#Cacheable(false)
public class ExampleRequest implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "EXAMPLE_REQUEST_ID_SEQ")
#Column(name="EXAMPLE_REQUEST_ID", nullable = false)
private Long exampleRequestId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exampleRequest")
private List<ExampleRequestYear> exampleRequestYearList;
public ExampleRequest() {
}
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
}
return this.exampleRequestYearList;
}
public void setExampleRequestYearList(List<ExampleRequestYear> exampleRequestYearList) {
this.exampleRequestYearList = exampleRequestYearList;
}
public ExampleRequestYear addExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().add(exampleRequestYear);
exampleRequestYear.setExampleRequest(this);
return exampleRequestYear;
}
public ExampleRequestYear removeExampleRequestYear(ExampleRequestYear exampleRequestYear) {
getExampleRequestYearList().remove(exampleRequestYear);
exampleRequestYear.setExampleRequest(null);
return exampleRequestYear;
}
}
#Entity
#Table(name = "EXAMPLE_REQUEST_YEAR")
#IdClass(ExampleRequestYearPK.class)
public class ExampleRequestYear implements Serializable {
#Id
#Column(nullable = false)
private Integer year;
#Id
#ManyToOne
#JoinColumn(name = "EXAMPLE_REQUEST_ID", referencedColumnName = "EXAMPLE_REQUEST_ID")
private ExampleRequest exampleRequest;
public ExampleRequestYear() {
}
public void setExampleRequest(ExampleRequest exampleRequest) {
this.exampleRequest = exampleRequest;
}
public ExampleRequest getExampleRequest() {
return exampleRequest;
}
}
Part of the code was auto-generated by the IDE and I'm still wrapping my head around JPA so there're probably design mistakes all around.
My app works (apparently) when I create a new ExampleRequest:
ExampleRequest exampleRequest = new ExampleRequest();
ExampleRequestYear exampleRequestYear = new ExampleRequestYear(2020);
request.addExampleRequestYear(exampleRequestYear);
However, I can't figure out how to edit an existing ExampleRequest because I'm unsure on how I'm meant to retrieve the linked entities. According to articles I've read, lazy fetching should be automatic, yet when I try this:
ExampleRequest exampleRequest = employeeRequestsController.getExampleRequestById(123);
System.out.println(exampleRequest.getExampleRequestYearList().size());
... I get a null pointer exception upon .size() because the getter runs but neither initialises an empty list, nor retrieves items from DB:
public List<ExampleRequestYear> getExampleRequestYearList() {
if(this.exampleRequestYearList == null){
// Field is null and conditional is entered
this.exampleRequestYearList = new ArrayList<ExampleRequestYear>();
// After initialisation, field is still null!
}
return this.exampleRequestYearList;
}
Also, switch to FetchType.EAGER solves this particular problem entirely. What am I missing?
Further details regarding app design. The Resource classes that handle HTTP requests interact with a set of Controller classes like this:
#Stateless(name = "ISomeActionController", mappedName = "ISomeActionController")
public class SomeActionController implements ISomeActionController {
#EJB
private IFooDAO fooDao;
#EJB
private IBarDAO barDao;
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId) {
return fooDao.getEntityById(exampleRequestId);
}
}
It's in the DAO classes where EntityManager is injected an used:
#Local
public interface IGenericDAO<T> {
public T persistEntity(T o);
public T persistEntityCommit(T o);
public void removeEntity(T o);
public void removeEntity(long id);
public T mergeEntity(T o);
public List<T> getEntitiesFindAll();
public List<T> getEntitiesFindAllActive();
public T getEntityById(Object id);
}
public interface IFooDAO extends IGenericDAO<ExampleRequest> {
public void flushDAO();
public ExampleRequest getExampleRequestById(Long exampleRequestId);
}
#Stateless(name = "IFooDAO", mappedName = "IFooDAO")
public class FooDAO extends GenericDAO<ExampleRequest> implements IFooDAO {
public FooDAO() {
super(ExampleRequest.class);
}
#Override
public void flushDAO(){
em.flush();
}
#Override
public ExampleRequest getExampleRequestById(Long exampleRequestId){
String sql = "...";
Query query = em.createNativeQuery(sql, ExampleRequest.class);
//...
}
}

How to get Hibernate JPA to manage list index automagically?

How do I setup a basic OneToMany relationship using a List and get Hibernate JPA to manage the sequence index number of the list automagically? Can this be done?
This is my test case (more or less);
#Table(name="Policy_Root")
public class PolicyRoot extends BaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(targetEntity=Policy.class, mappedBy="policyRoot", cascade=CascadeType.ALL)
#IndexColumn(name="policy_sequence", base=0, nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
public void addPolicy(Policy policy) {
policyList.add(policy);
policy.setPolicyRoot(this);
}
public void addPolicy(int sequence, Policy policy) {
policyList.add(sequence, policy);
policy.setPolicyRoot(this);
}
}
#Entity()
#Table(name="Policy")
public class Policy extends BaseDomainModel {
/** The position of this policy record within the list of policy's belong to the parent PolicyRoot */
private int policySequence;
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence")
public int getPolicySequence() {
return policySequence;
}
public void setPolicySequence(int policySequence) {
this.policySequence = policySequence;
}
#ManyToOne
#JoinColumn(name="policy_root_oid", nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}
#Test
public void testCreation() {
Policy policy1 = new Policy();
Policy policy2 = new Policy();
// Uncomment the following and the test case works - but I don't want to manage the sequence numbers
//policy2.setPolicySequence(1);
PolicyRoot policyRoot = new PolicyRoot();
policyRoot.addPolicy(policy1);
policyRoot.addPolicy(policy2);
ServiceImplFacade.getPersistenceFacade().persistSingleItem(policyRoot);
Long oid = policyRoot.getOid();
PolicyRoot policyRootFromDB = ServiceImplFacade.getPersistenceFacade().getEntityManager().find(PolicyRoot.class, oid);
assertEquals(2, policyRootFromDB.getPolicyList().size());
}
If I uncomment the policy2.setPolicySequence(1); line then the test case passes, but I don't think I need to do this. I want Hibernate to do this for me. My understanding is that it can, but if it can't then knowing that it can't would be a good answer as well.
I've tried various combinations of setting nullable, insertable and updateable but I may have missed one.
Is this possible? - If so how?
Found the answer, - it was around getting the right combinations of nullable and insertable. Also had to make the "child index" at Integer so that it could be nullable, and there's also an "optional" flag in the following as well.
public class PolicyRoot extends BordereauxBaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(cascade=CascadeType.ALL)
#IndexColumn(name="policy_sequence", nullable=false, base=0)
#JoinColumn(name="policy_root_oid", nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
}
public class Policy extends BordereauxBaseDomainModel {
/** The position of this policy record within the list of policy's belong to the parent PolicyRoot */
private Integer policySequence;
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence", insertable=false, updatable=false)
public Integer getPolicySequence() {
return policySequence;
}
public void setPolicySequence(Integer policySequence) {
this.policySequence = policySequence;
}
#ManyToOne(optional=false)
#JoinColumn(name="policy_root_oid", insertable=false, updatable=false, nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}
Found the answers on the following page after searching Google for a while.
http://opensource.atlassian.com/projects/hibernate/browse/HHH-4390
Do something like this:
#Entity
class Parent {
#OneToMany
#IndexColumn(name = "index_column")
List<Child> children;
}
#Entity
class Child {
#ManyToOne
Parent parent;
#Column(name = "index_column")
Integer index;
#PrePersist
#PreUpdate
private void prepareIndex() {
if (parent != null) {
index = parent.children.indexOf(this);
}
}
}
I'm going to post this answer since I recently had the same issue and this question, although outdated, keeps coming up in the researches.
The #IndexColumn annotation has been deprecated a long time ago and in its place it is best recommended using the #OrderColumn annotation. The second annotation not only simplifies its syntax without having to specify the base attribute, but it also avoids declaring an extra field in the detail class, in this case the policySequence field within the Policy class.
Here is the updated version of the previous snippet:
#Table(name="Policy_Root")
public class PolicyRoot extends BaseDomainModel {
private List<Policy> policyList = new ArrayList<Policy>();
#OneToMany(targetEntity=Policy.class, mappedBy="policyRoot", cascade=CascadeType.ALL)
#OrderColumn(name="policy_sequence", nullable=false)
public List<Policy> getPolicyList() {
return policyList;
}
public void setPolicyList(List<Policy> policyList) {
this.policyList = policyList;
}
public void addPolicy(Policy policy) {
policyList.add(policy);
policy.setPolicyRoot(this);
}
public void addPolicy(int sequence, Policy policy) {
policyList.add(sequence, policy);
policy.setPolicyRoot(this);
}
}
#Entity()
#Table(name="Policy")
public class Policy extends BaseDomainModel {
//No need to declare the policySequence field
/** Birectional pointer to parent */
private PolicyRoot policyRoot;
#Column(name="policy_sequence")
public int getPolicySequence() {
return policySequence;
}
public void setPolicySequence(int policySequence) {
this.policySequence = policySequence;
}
#ManyToOne
#JoinColumn(name="policy_root_oid", nullable=false)
public PolicyRoot getPolicyRoot() {
return policyRoot;
}
public void setPolicyRoot(PolicyRoot policyRoot) {
this.policyRoot = policyRoot;
}
}

Categories