Hello have problems with saving relation ships with NEO4J and Spring, I can save relationships when I don't use a class with #RelationProperties, but whenever I want to use that class the relationships are not even created.
I am using Neo4j 4.3.7-community edition in a docker container
And in my pom.xml i have
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>6.1.5</version>
</dependency>
I want to do something like this
Image
I have this bit of code :
starting node
...
#Relationship(type = "VISITS",direction = Relationship.Direction.OUTGOING)
public Set<Visits> visits;
public void visitPOI(Visits visit) {
if(visits == null) {
visits = new HashSet<>();
}
visits.add(visit);
}
...
My relation
#RelationshipProperties
public class Visits {
#Id #GeneratedValue
private long id;
#Property private String description;
#TargetNode private PointOfInterest poi;
public Visits(String description, PointOfInterest p){
this.description = description;
this.poi = p;
}
...
and the destination node
...
#Node
public class PointOfInterest {
#Id #GeneratedValue(generatorClass = UUIDStringGenerator.class)
private String id;
#Property private String name;
#Property private double lat;
#Property private double lng;
public PointOfInterest() { }
//getters and setters
...
And this in a service
...
public void saveRelation(String username, Visits v ){
User u = userRepository.findByUserName(username);
Optional<PointOfInterest> poi = pointOfInterestRepository.findById(v.getPoi().getId());
v.setPoi(poi.get());
u.visitPOI(v);
userRepository.save(u);
}
...
and the JSON I post
{
"description":"Nice trip",
"poi":{
"id":"Uster"
}
}
Use #RelationshipId on id field of #RelationshipProperties as follow
#RelationshipProperties
public class Visits {
#RelationshipId
private long id;
#Property private String description;
.....
}
I had the same issue with persisting relationship properties using #RelationshipProperty annotated class. Finally, I discovered that the issue is due to the implicit conversion between long and Long. Changing #RelationshipId private long id; to #RelationshipId private Long id; inside the #RelationshipProperty annotated class will do the trick. This appears to be a bug in SDN, it should report an error instead.
Related
I'm new with JOOQ library and have one thing interesting me so much. I've implemented CRUD service on JOOQ at first and after that I've tried to avoid some duplicate code. For reach that goal I've added JPA repository and also added#Entity annotation to my generated by JOOQ class. And now I still want to use JOOQ for some cases (querying List using filter and sorting and pagination). But something went wrong and now after JOOQ makes a select request I can see nulls in my class's attributes.
I'm getting right count of entities by filter, but class's properties are null after mapping. Is that mapping wrong or I just could't use JOOQ and JPA together for this case?
My abstact class for all entities (as I said, for avoid duplicating code I've refactored some code and now use generics):
#MappedSuperclass
public abstract class AbstractServiceEntity {
private Integer id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
My JPA class (generated by JOOQ):
/**
* This class is generated by jOOQ.
*/
#SuppressWarnings({ "all", "unchecked", "rawtypes" })
#Entity
#Table(schema = "ref", name = "account")
public class Account extends AbstractServiceEntity implements Serializable {
private static final long serialVersionUID = -162537472;
private Integer id;
private Integer transitId;
private Integer partnerId;
private String currencyCode;
private String descr;
private Long inCredit;
private Long balanceLimit;
private Long outCredit;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private Integer transitPartnerId;
public Account() {}
public Account(Account value) {
this.id = value.id;
this.transitId = value.transitId;
this.partnerId = value.partnerId;
this.currencyCode = value.currencyCode;
this.descr = value.descr;
this.inCredit = value.inCredit;
this.balanceLimit = value.balanceLimit;
this.outCredit = value.outCredit;
this.createdAt = value.createdAt;
this.updatedAt = value.updatedAt;
this.transitPartnerId = value.transitPartnerId;
}
public Account(
Integer id,
Integer transitId,
Integer partnerId,
String currencyCode,
String descr,
Long inCredit,
Long balanceLimit,
Long outCredit,
LocalDateTime createdAt,
LocalDateTime updatedAt,
Integer transitPartnerId
) {
this.id = id;
this.transitId = transitId;
this.partnerId = partnerId;
this.currencyCode = currencyCode;
this.descr = descr;
this.inCredit = inCredit;
this.balanceLimit = balanceLimit;
this.outCredit = outCredit;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
this.transitPartnerId = transitPartnerId;
}
And my method extracting entities from DB:
#Repository
#RequiredArgsConstructor
public class JooqAccountRepository {
private final DSLContext jooq;
public List<Account> findAll(Condition filterCondition, SortField[] sortFields, Integer partnerId, Integer limit, Integer offset) {
return jooq.selectFrom(ACCOUNT)
.where(ACCOUNT.PARTNER_ID.equal(partnerId))
.and(filterCondition)
.orderBy(sortFields)
.limit(limit)
.offset(offset)
.fetchInto(Account.class);
}
public Integer findAccountsCount(Integer partnerId) {
return jooq.selectCount().from(ACCOUNT)
.where(ACCOUNT.PARTNER_ID.equal(partnerId))
.fetchOne(0, Integer.class);
}
}
As a result of my searches - I've made a mistake with annotations in Account class. If you want use these frameworks together, you should use #Column on entity's properties or setting your jooq's codegen plugin in different way)
This resource was usefull for me
I am learning spring boot. As a part of this learning, I am creating a invoicing application's backend using REST Webservices. Here is my JSON Request.
{
"invoice": {
"invoiceNumber":"KB123456",
"custId":"123",
"pricingId":"234",
"empId":"456",
"gstCode":"HSN1234",
"invoiceSubTotal":"1234.00",
"invoiceGst":"18%",
"invoiceGstAmount":"123.45",
"invoiceTotal":"1357.45",
"invoiceLineItems": [
{
"invoiceLineNo":"KB123456-1",
"invoiceLineDesc":"Des123",
"invoiceQty":"2",
"invoicePpu":"0.18",
"invoiceGstCode":"HSN1235",
"invoiceGstPercentage":"18%",
"invoiceLineAmount":"123",
"invoiceLineTaxAmount":"12",
"invoiceLineTotalAmount":"135"
},
{
"invoiceLineNo":"KB123456-2",
"invoiceLineDesc":"Des124",
"invoiceQty":"4",
"invoicePpu":"0.17",
"invoiceGstCode":"HSN1235",
"invoiceGstPercentage":"18%",
"invoiceLineAmount":"126",
"invoiceLineTaxAmount":"14",
"invoiceLineTotalAmount":"140"
}
]
}
}
invoiceLineItems can house up to 15 objects.
Here are my Entities Invoice and InvoiceLineItems
Invoice.Java
#Entity
public class Invoice {
#Id
#GeneratedValue
private long invoiceId;
private String invoiceNumber;
private long custId;
private long pricingId;
private long empId;
private String gstCode;
private String invoiceSubTotal;
private String invoiceGst;
private String invoiceGstAmount;
private String invoiceTotal;
#Transient
#OneToMany(mappedBy="invoice")
private List<InvoiceLineItems> invoiceLineItems;
}
InvoiceLineItems.Java
#Entity
public class InvoiceLineItems {
#Id
#GeneratedValue
private long invoiceLineId;
private String invoiceNumber;
private String invoiceLineNo;
private String invoiceLineDesc;
private String invoiceQty;
private String invoicePpu;
private String invoiceGstCode;
private String invoiceGstPercentage;
private String invoiceLineAmount;
private String invoiceLineTaxAmount;
private String invoiceLineTotalAmount;
#ManyToOne
private Invoice invoice;
}
Here is my REST Controller
#RestController
public class InvoiceResourceController {
#Autowired
InvoiceRepository invoiceRepository;
InvoiceLineRepository invoiceLineRepository;
#PostMapping("/invoice")
public ResponseEntity<Invoice> createInvoice(#RequestBody Invoice invoice) {
List<InvoiceLineItems> invoiceLineItemsList = invoice.getInvoiceLineItems();
Invoice savedInvoice = invoiceRepository.save(invoice);
/* for (int i = 0; i < invoiceLineItemsList.size(); i++) {
InvoiceLineItems invLn = new InvoiceLineItems();
invLn = invoiceLineItemsList.get(i);
invoiceLineRepository.save(invLn);
} */
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}")
.buildAndExpand(savedInvoice.getInvoiceNumber()).toUri();
return ResponseEntity.created(location).build();
}
}
Now, I have two tables Invoice and Invoice_Line_Items created by Spring Boot. I am able to persist the Invoice Entity to the Invoice table, but I'm struggling to get the InvoiceLineItems Array Object from request and persisting to Invoice_Line_Items table.
Any help will be appreciated.
Solved it.
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "invoiceNumber")
private List<InvoiceLineItems> invoiceLineItems;
I have a CrudRepository that is supposed to make a query with an array (findByIn). In my repository tests it works, but when I try to use the query in my service, it doesn't work. Could someone explain why it doesn't work? Here is my setup (excluding some code irrelevant to the question)
Database model:
#Entity
#Table(name="Place")
public class Place implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "placeId", nullable = false)
private Long placeId;
#Column(name = "owner", nullable = false)
private String owner;
public Long getPlaceId() {
return placeId;
}
public void setPlaceId(Long placeId) {
this.placeId = placeId;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
Repository:
#Repository
public interface PlaceRepository extends CrudRepository<Place, Long> {
List<Place> findByPlaceIdIn(Long[] placeId);
}
Service (this is the part not working):
#Service
public class PlaceService {
#Autowired
private PlaceRepository placeRepository;
public List<Place> getPlaces(Long[] placeIds) {
return placeRepository.findByPlaceIdIn(placeIds);
}
}
The problem is that in my service placeRepository.findByPlaceIdIn(placeIds) returns 0 objects if placeIds contains more than one item. If placeIds contains just one item, the query works fine. I tried replacing return placeRepository.findByPlaceIdIn(placeIds) with this piece of code that does the query for every array item one by one (this actually works, but I'd like to get the query work as it should):
ArrayList<Place> places = new ArrayList<Place>();
for (Long placeId : placeIds) {
Long[] id = {placeId};
places.addAll(placeRepository.findByPlaceIdIn(id));
}
return places;
I know that the repository should work, because I have a working test for it:
public class PlaceRepositoryTest {
#Autowired
private PlaceRepository repository;
private static Place place;
private static Place place2;
private static Place otherUsersPlace;
#Test
public void testPlacesfindByPlaceIdIn() {
place = new Place();
place.setOwner(USER_ID);
place2 = new Place();
place2.setOwner(USER_ID);
place = repository.save(place);
place2 = repository.save(place2);
Long[] ids = {place.getPlaceId(), place2.getPlaceId()};
assertEquals(repository.findByPlaceIdIn(ids).size(), 2);
}
}
I also have another repository for other model, which also uses findByIn and it works fine. I can't see any relevant difference between the repositories. I thought it might offer some more details to show the working repository, so I included it below:
Database model:
#Entity
#Table(name="LocalDatabaseRow")
#JsonIgnoreProperties(ignoreUnknown=false)
public class LocalDatabaseRow implements Serializable {
public LocalDatabaseRow() {}
public LocalDatabaseRow(RowType rowType) {
this.rowType = rowType;
}
public enum RowType {
TYPE1,
TYPE2
};
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
#JsonProperty("id")
private Long id;
#JsonProperty("rowType")
#Column(name = "rowType")
private RowType rowType;
public Long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public RowType getRowType() {
return rowType;
}
public void setRowType(RowType rowType) {
this.rowType = rowType;
}
}
Repository:
#Repository
public interface LocalDatabaseRowRepository extends CrudRepository<LocalDatabaseRow, Long> {
List<LocalDatabaseRow> findByRowTypeAndUserIdIn(RowType type, String[] userId);
}
try using a list instead :
findByPlaceIdIn(List placeIdList);
You have a typo in your code (the repository declaration in the service):
#Autowired
private placeRepository placeRepository;
Should be:
#Autowired
private PlaceRepository placeRepository;
Have to beans:
#Entity
#Table(name="book")
public class Book {
#Id
#Column(name="id_book")
#GeneratedValue(generator="increment")
#GenericGenerator(name="increment", strategy="increment")
private int id;
#Column
#Size(min=1,max=100)
private String title;
#Column
#Size(min=1,max=400)
private String description;
#Column
private Integer year=0;
#ManyToMany(cascade={CascadeType.ALL},fetch = FetchType.EAGER)
#Fetch (FetchMode.SELECT)
#JoinTable(name="book_author",
joinColumns={#JoinColumn(name="book_id_book")},
inverseJoinColumns= {#JoinColumn(name="author_id_author")})
private List<Author> author=new ArrayList<Author>();
//getters/setters
}
and:
#Entity
#Table(name="author")
public class Author {
#Id
#Column(name="id_author")
#GeneratedValue
private Integer id;
#Column
private String name;
#Column
private String surname;
#ManyToMany(mappedBy="author")
private Set<Book> book=new HashSet<Book>();
//getters/setters
}
In my jsp I'm have form for enter data about book, and multiple list for select author(s) from DB, problem only in select authors, therefore give only this code:
<sf:select multiple="true" path="author" items="${authors}" size="7" >
</sf:select>
Where ${authors} - List with objects Author from DB. Use POST request.
In my controller for this page have this (I know it's not correct):
#RequestMapping(value="/addbook", method=RequestMethod.POST)
public String addBook(Book book){
hibarnateService.saveBook(book);
return "redirect:/books";
}
When I'm create book without select authors, but enter another information, all fine, book save in DB. When select some authors get this - The request sent by the client was syntactically incorrect.
Problem solved by add in controller:
#InitBinder
protected void initBinder(WebDataBinder binder){
binder.registerCustomEditor(Author.class, new Editor(hibarnateService));
}
and create class:
public class Editor extends PropertyEditorSupport {
private final Dao hibernateService;
public Editor(Dao hibernateService){
this.hibernateService=hibernateService;
}
#Override
public void setAsText(String text) throws IllegalArgumentException{
Author author=hibernateService.getAuthor(Integer.parseInt(text));
setValue(author);
}
}
P.S. What wrong with me? I can't find the right answer myself until I ask here)
You will need to implement initBinder in your controller, below can be tentative code (not tested)
#InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(List.class, "authors ", new CustomCollectionEditor(List.class)
{
#Override
protected Object convertElement(Object element)
{
Long id = null;
if(element instanceof Long) {
//From the database 'element' will be a Long
id = (Long) element;
}
return id != null ? authorService.loadAuthorById(id) : null;
}
});
}
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.