current/previous node relations in spring data neo4j (LinkedList) - java

I’m trying to create linked list of nodes with CURRENT/PREVIOUS relation similar to the picture below.
I'm not sure if my solution is the right way to handle this scenario, but to achieve that I created two nodes with a single method to populate new messages as below:
#Builder
#Data
#NoArgsConstructor
public class Person {
#Id
#GeneratedValue
private Long id;
private String name;
#Relationship(type = "LATEST")
private Message message;
void newMessage(Message newMessage) {
newMessage.setPrevious(message);
message = newMessage;
}
}
#Builder
#Data
#NoArgsConstructor
public class Message {
#Id
#GeneratedValue
private Long id;
private String text;
#Relationship(type = "PREVIOUS")
private Message previous;
}
I also created a sample code to test this solution:
#SpringBootApplication
public class NewsFeedApplication {
public static void main(String[] args) {
SpringApplication.run(NewsFeedApplication.class, args);
}
#Bean
CommandLineRunner init(PersonRepository personRepository) {
return args -> {
Person personToAdd1 = Person.builder().name("John").build();
personToAdd1.newMessage(Message.builder().text("first message").build());
personToAdd1.newMessage(Message.builder().text("second message").build());
personToAdd1.newMessage(Message.builder().text("third message").build());
personRepository.save(personToAdd1);
personToAdd1.newMessage(Message.builder().text("New message.").build());
personRepository.save(personToAdd1);
};
}
}
I feel like I'm close, but I don't know how to reset the previous CURRENT relation and my solution produces output as:
So the question is:
If my approach is okay, how could I remove previous CURRENT relation.
If this approach is wrong, how could I implement linked list with CURRENT/PREVIOUS relations for nodes correctly.

I found the missing puzzle, i.e. detaching the supplier relation. I don't know why I assumed in advance that this relation should be deleted automatically by the spring data repository "save" method.
Working solution:
public interface PersonRepository extends Neo4jRepository<Supplier, Long> {
#Query("MATCH (n:Person {name: $name})-[r:LATEST]->() DELETE r")
void detachLatestFromPerson(String name);
}
void newMessage(PersonRepository personRepository, Message newMessage) {
personRepository.detachLatestFromPerson(name);
newMessage.setPrevious(message);
message = newMessage;
}
PS. I still have doubts, as I'm not sure if that's a good approach to handle this scenario, so if you know a better solution, go ahead and post it, we can always swap the 'best answer' :)

Related

JPA doesn't save the Associated Object in Object gotten by #RequestBody annotation

following is Developer Entity.
Developer
#Entity
#Getter
#Setter
public class Developer {
#Id
#GeneratedValue
#Column(name="DEVELOPER_ID")
private Long id;
private String nickname;
private String name;
private String imageURI;
#OneToMany(mappedBy = "developer", cascade={CascadeType.PERSIST})
private List<Article> articleList = new ArrayList<>();
public void addArticle(Article article) {
this.articleList.add(article);
if ( article.getDeveloper() != this )
article.setDeveloper(this);
}
}
and following is Article Entity.
Article
#Entity
#Getter
#Setter
public class Article {
#Id
#GeneratedValue
#Column(name="ARTICLE_ID")
private Long id;
private String subject;
#ElementCollection
private List<String> contents = new ArrayList<>();
public void addContent(String content) {
this.contents.add(content);
}
#ManyToOne
#JoinColumn(name="DEVELOPER_ID")
private Developer developer;
public void setDeveloper(Developer developer) {
this.developer = developer;
if ( !developer.getArticleList().contains(this) )
developer.getArticleList().add(this);
}
}
view request to RestController (/about/developers/rest/add) via ajax.
and following is Request Body's json.
request body json
[
{"articleList":[
{"contents":["article line","article line"],
"subject":"article subject"},
{"contents":["article line","article line","article line"],
"subject":"article subject"}
],
"nickname":"dev nickname",
"name":"(dev name)",
"imageURI":"default.png"
},
{"articleList":[
{"contents":["article line","article line"],
"subject":"article subject"},
{"contents":["article line"],
"subject":"article subject"}
],
"nickname":"dev nickname",
"name":"(dev name)",
"imageURI":"default.png"}
]
RestController that including #RequestBody
RestController
#RestController
#RequestMapping("/about")
public class AboutRestController {
#Autowired
private DeveloperService developerService;
#PostMapping("/developers/rest/add")
public void developersRegisterAPI(#RequestBody List<Developer> developerList) {
/** Not Working **/
// Saving Developer only (except Associated Articles)
for (Developer developer : developerList) {
developerService.save(developer);
}
/** Working **/
// Saving Developers and Associated Articles
for (Developer developer : developerList) {
Developer newDeveloper = new Developer();
newDeveloper.setName(developer.getName());
newDeveloper.setImageURI(developer.getImageURI());
newDeveloper.setNickname(developer.getNickname());
for (Article article : developer.getArticleList()) {
Article newArticle = new Article();
newArticle.setSubject(article.getSubject());
newArticle.setContents(article.getContents());
newDeveloper.addArticle(newArticle);
}
developerService.save(newDeveloper);
}
}
}
when i printed #RequestBody List<Developer> developerList, each Developer object has associated Article objects.
in first case (not working properly, developerService.save(developer);), developerService.findAll()'s result including only Developer Objects. there are not Article objects in retrieved Developer objects.
in second case (working properly, developerService.save(newDeveloper);), developerService.findAll()'s result including both Developer Objects and associated Article objects.
in fact, both cases are saving associated Articles in RestController's method.
but in web controller's method, when i used developerService.findAll(), there aren't articles. so if i passed retrieved Developer Objects via Model, there aren't Associated Article and view can not print these objects.
and following is DeveloperService
DeveloperService and DeveloperRepository
#Service
public class DeveloperService {
#Autowired
DeveloperRepository developerRepository;
public void save(Developer developer) {
developerRepository.save(developer);
}
public void delete(Developer developer) {
developerRepository.delete(developer);
}
public List<Developer> findAll() {
return developerRepository.findAll();
}
}
#Repository
#Transactional
public interface DeveloperRepository extends JpaRepository<Developer, Long> {
}
and following is ARTICLE Table in h2-console
ARTICLE Table in h2-console (click me for image)
as you can see, DEVELOPER_ID is null.
can i fix this only using
for (Developer developer : developerList) {
developerService.save(developer);
}
?
first will not work because in Articles list in json does not have Developer Object bound. the second case is working because of this line
newDeveloper.addArticle(newArticle);
in which you are adding
if ( article.getDeveloper() != this )
article.setDeveloper(this);
try to bind developer object in your article list in the first loop and your code will work. i mean try to bind developer object in each article object from the list

how to cascade GroupSequenceProvider to member List?

Hi say I have sample bean called car and I want to redefine its validation sequence:
#GroupSequenceProvider(value = CarSequenceProvider.class)
public class Car{
#NotNull(groups = {groupOne.class})
private Boolean isGood;
#Valid
private List<Driver> drivers;
// getter/setter
}
this is my driver class
public class Driver{
#NotEmpty(groups = {groupTwo.class})
private List<String> skills;
//getter/setter
}
and here goes my sequence provider:
public class CarSequenceProvider implements DefaultGroupSequenceProvider<Car>{
#Override
public List<Class<?>> getValidationGroups(Car car) {
List<Class<?>> sequence = new ArrayList<Class<?>>();
sequence.add(Car.class);
sequence.add(groupOne.class);
if(car != null && car.IsGood()){
sequence.add(groupTwo.class);
}
}
Basically I only want All drivers skills not to be empty if the car is good. What is happening right now is that #notEmpty never gets called because my sequence redefinition is not cascaded to Driver List. Is it possible to make it cascade then?
Thank you so much
Here #ConvertGroup comes into play which allows you during cascaded
validation to use a different group than the originally requested one.
Is that annotation you tried
Source: example-group-conversion-dedault-to-driver-checks

Glassfish server cache not getting updated when using #XmlAdapter?

I've stumbled upon a strange problem using a #XmlAdapter. Let me try to sketch the situation:
At the server side I have a class Cows:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class Cows implements Serializable {
...a bunch of cow properties like ID, name, ...
#XmlElementWrapper(name = "sampless")
#XmlElement(name = "samples")
#OneToMany(mappedBy = "cowId")
private List<Samples> samplesList;
...a bunch of getters & setters...
}
I have a class Samples:
#XmlAccessorType(XmlAccessType.FIELD)
#XmlRootElement
public class Samples implements Serializable {
...a bunch of samples properties like ID, cellcount, ...
#JoinColumn(name = "cow_id", referencedColumnName = "id")
#ManyToOne
#XmlJavaTypeAdapter(CowsAdapter.class)
private Cows cowId;
}
CowsAdapter class:
public class CowsAdapter extends XmlAdapter<Cows, Cows> {
#Override
public Cows unmarshal(Cows v) throws Exception {
return v;
}
#Override
public Cows marshal(Cows v) throws Exception {
return null;
}
}
I include a list of samples when a Cow object is requested by the client as shown above.
Now, at client side, when I try to add a new Sample for a Cow, it marshals correctcly into a xml string (including the Cow object) which is send to the webservice. A new entry is correctly added to the database.
However, when I request the same Cow again (with the samplesList included), the new Sample is not added althou it is in the database? When I restart the glassfish instance, it shows.. Why is this and should I use another approach to avoid this?
Edit: same behaviour happens when deleting samples. It gets deleted from the database but is still included in the response of a Cow request. This has something to do with the cache of glassfish?
Edit2: I found some kind of solution for this. I changed the findById method as follows:
#GET
#Path("cow/{id}")
#Produces({"application/xml"})
public Cows findById(#PathParam("id") Integer id) {
//Cows cow = super.find(id);
final Query qry = getEntityManager().createNamedQuery("Cows.findById", Cows.class);
qry.setParameter("id", id);
qry.setHint("javax.persistence.cache.storeMode", "REFRESH");
Cows cow = (Cows) qry.getSingleResult();
return cow;
}
However, could someone let me know if this is the best approach?
Thanks in advance.
Disabled the Glassfish cache for now by adding a property to persistence.xml
You need to maintain both sides of a relationship. If you create a new Sample you need to add it to its Cow's samples.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side

Saving entities fetched from a query with hibernate

I hava a basic Hibernate/JPA question. I want to find a best practice solution for saving entities. I have a List of Entities and many of them might be altered so I want to save them all at once.
I believe everything is pretty much standard. (Just example code for readability reasons)
Entity: Car
#Entity
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private id;
private String model;
// .... Setter
// .... Getter
}
Service Class: CarService
#Named
#Transactional
public class CarServiceImpl implements CarService {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Car> findAll() {
TypedQuery<Car> q = entityManager.createQuery(
"FROM Car", Car.class);
return q.getResultList();
}
#Override
public void saveEntity (Car car) {
/* What exactly am I doing here? */
}
}
Controller: CarEditController
#Named
public class CarEditController implements Serializable {
private static final long serialVersionUID = 1L;
#Inject
private CarService carService;
private List<Car> cars;
public List<Car> getCars () {
return carService.findAll();
}
public void setCars (List<Car> cars) {
this.cars = cars;
}
public void btn_newClick() {
Car newCar = new Car();
car setModel("BMW");
cars.add(newCar);
}
public void btn_saveClick() {
for (Car car : cars) {
carService.saveEntity(car);
}
}
}
I found quite a few ways of saving the entity. The obvious are entityManager.merge(car) for existing entities and entityManager.persist(car) for new ones. In theory thats easy but how do I know which entity is new?
The documentation suggests entityManager.flush(), which in theory updates all existing and inserts all new entities. It works but only if I fetch the entity with find().
The Question:
I want to fetch all entities in one step, add new ones and then save them all in one methode (btn_saveClick()). How is this task best accomplished?
Just check if the #Id is been set.
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
} else {
entityManager.merge(car);
}
}
More common approach, however, is to offer separate create() and update() service methods.
I'm not familiar with JPA but in hibernate there is session.saveOrUpdate()
for(Car car : existingAndNewCars)
{
session.saveOrUpdate(car);
}
Update:
As i understand JPA, its merge is like session.merge which is totally different as it doesn't track changes to object supplied to it while persist/save/update/saveOrUpdate would track subsequent changes to car, leading to subtle differences
Update:
since you use the same entitymanager it should suffice to
#Override
public void saveEntity (Car car) {
if (car.getId() == null) {
entityManager.persist(car);
}
without the subtle difference of persist and merge
The flush operation will operate on all entities in the current Hibernate session - new entities are inserted and existing entities are updated if they have changed.
You need to ensure that all entities are attached to the session. You do this by using merge as you correctly say. After you have loaded all of the entities the session is closed when the transaction ends. Your objects are then in a detached state i.e. have been persisted but are no longer attached to a session.
I would amend your logic so that your carService#save takes a List. It can then call merge on each one (attaching them to the session). Then when your transaction ends Hibernate will flush all changes to the database at once.

My LinkedList is not saved as it should be (regarding its elements order)

First of all, pardon me for my poor english level. I will try to be as understandable as I can.
I am trying to re-order a playlist of music files. A Playlist is basically a LinkedList<MusicFiles> with a name.
I change the position of an element, it seems to be as it should, cool. But when I save it in the database the order doesn't change! I am doing something wrong, that's a fact, but after hours spent debugging, my mind could really use a debugger for itself...
Here is my jsf code (inside a p:datatable):
<p:commandButton title="Move Down"
ajax="false"
image="down"
action="#{playlistMBean.increasePosition(musicFile.URL)}"
onclick="show_my_playlists?face-redirect=true"/>
The backing bean code:
#Named(value = "playlistMBean")
#SessionScoped
public class PlaylistMBean implements Serializable {
#EJB
private PlaylistManager playlistManager;
private Playlist currentPlaylist;
//...
public void increasePosition(String musicURL) {
currentPlaylist.increasePosition(musicURL);
playlistManager.save(currentPlaylist);
}
//...
}
"currentPlaylist" is obviously a Playlist, so here's the code of the method in the entity bean "Playlist":
#Entity
#NamedQueries(/*...*/)
public class Playlist implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#OneToMany(cascade= {CascadeType.REFRESH, CascadeType.MERGE},fetch= FetchType.EAGER)
#OrderBy(/* ??????????? */
private LinkedList<MusicFile> musicList;
private String name;
//...
public void increasePosition(String url) {
if (url != null) {
MusicFile mf = getMusicFileByURL(url);
int position = getPosition(url);
if (position < musicList.size() - 1) {
musicList.remove(position);
musicList.add(position + 1, mf);
}
}
}
And finally the code of the playlist manager which should save the reordered playlist in the database:
#Stateless
#LocalBean
public class PlaylistManager {
#PersistenceContext(unitName = "Doozer-ejbPU")
private EntityManager em;
public void save(Playlist playlist) {
Playlist p = em.find(Playlist.class, playlist.getId());
if (p != null) {
em.merge(playlist);
}
else {
em.persist(playlist);
}
}
//...
}
The given playlist in this last step is the good one (reordered). So my guess is that my problem is with the entity manager (I'm such a genius, I know...).
Does anyone know why?
May it comes from the cascade type? Due to my lack of knowledge about it I'm not sure which one should I put. I have tried CascadeType.ALL but then it raises exception when adding music files. CascadeType.DETACH was my first choice since I dont't want to delete the musics when deleting a playlist... But here again I really don't know for sure if I know what I'm talking about :(
[Edit]: Thanks to Piotr Nowicki, my question has changed quite a lot: how can I define the #OrderBy annotation in order to sort the LinkedList according to its inner order? Is it even possible? The easy/ugly method would be to add a property position to the MusicFile entity but I'd rather not.
If you want to preserve the order in which elements in List are stored in the database (and further retrieved from it) you should use #OrderBy or #OrderColumn annotations.
In your case, if you just want the List to be returned in order without any advanced conditions, the #OrderColumn should be sufficient:
#OneToMany(...)
#OrderColumn
private LinkedList<MusicFile> musicList;

Categories

Resources