Neo4j eager/lazy loading with Spring data - spring

I'm investigating Neo4j and have a question with regards to object eager/lazy loading. Lets say I have class Trolley with has Set<Item> (with getters/setters). If I do the following:
Trolley t = new Trolley(...); // create empty trolley
t.addItem(f); // add one item to the trolley
t.persist(); // persist the object
I then later find the trolley based on the nodeId:
repo.findOne(xxx); // returns the trolley successfully
When I do something like:
trolley.getItems().size()
this is empty. I guess this is the intended behaviour. Is there any mechanism similar to JPA where is the session/tx is open to load the collection dynamically.
Code:
#NodeEntity
public class Trolley
{
#Indexed
private String name;
#RelatedTo
private Set<Item> items;
public Trolley(){}
public Trolley(String name)
{
this.name = name;
}
public void addItem(Item item)
{
this.items.add(item);
}
public Set<Item> getItems()
{
return items;
}
}
#NodeEntity
public class Item
{
private String name;
public Item(){}
public Item(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
#Test
public void trolleyWithItemPersist()
{
Trolley trolley = new Trolley("trolley1").persist();
// Persisting - however I would've expected a cascade to
// occur when adding to the collection.
Item item = new Item("item1").persist();
// now add to the trolley
trolley.addItem(item);
// persist
trolley.persist();
// Now use repo to get trolley
Trolley loadedTrolley = trolleyRepository.findOne(trolley.getNodeId());
// should have one item
assertEquals(1, loadedTrolley.getItems().size());
}

Afaik, in Spring Data Jpa, to populate an lazy loaded field you need to annotate the method which call the findOne(xxx) with
#Transactional
from (org.springframework.transaction.annotation.Transactional)
Maybe it works also with neo4j...
I'm not really an skilled developper on Spring Data but this is the only way I know to retrieve lazy loaded fields. If someone has a better solution, feel free to write it!

Related

Use converter to get form data parameters into a map inside a dto

I have a controller method in spring boot:
#PostMapping(produces = "text/html")
public String create(#Valid myDTO myDTO, BindingResult bindingResult, Model uiModel, HttpServletRequest httpServletRequest) {
// Omissions
return "redirect:/blue/tar/";
}
I have a dto:
public class MyDTO {
private Long id;
private List<FooBar> objects;
public MyDTO(Long id, List<FooBar> objects) {
this.id = id;
this.objects = objects;
}
public Long getId() {
return id;
}
public List<FooBar> getObjects() {
return objects;
}
}
And I have a converter, which i have added to the converterRegistry in spring:
private Converter<String, FooBar> getStringToLegalEntityConverter() {
return new org.springframework.core.convert.converter.Converter<>() {
public FooBar convert(#NotNull String id) {
return //* convert id to FooBar structure*//
}
};
}
My web form send form data. E.g:
fooBar: 1
fooBar: 3
fooBar: 4
And the above code handles this perfectly. The converter is called three times with 1, 3 and 4. The result a bit magically appears as a list with three FooBar objects in the dto in the controller.
The above works.
I attempted to use a map in MyDTO instead. Spring complains that there is no converter from String -> Map.
If i add one (which feels wrong, since there was no converter from String -> List before), it only enters the converter once with the last value. (4, in the example above).
So is there a solution here to get spring to allow me to manually convert a series of form params to a map in such a way that they end up in a DTO like that?
Below is my attempt which fails. Since spring only attempts to convert one value to fit in the map in the DTO. I would have needed the converter to be called with all the form parameters at once:
private Converter<String, Map<Long, FooBar>> attempt() {
return new org.springframework.core.convert.converter.Converter<>() {
public Map<Long, FooBar> convert(#NotNull String aString) {
/* Say if aString is the formparameters as "1,3,4" then i'd turn those into keys and then fetch their values from a db */
return /* The map */
}
};
}
Similar but I don't think the answers there are applicable: How to get Form data as a Map in Spring MVC controller?

list of elements in configurationproperties and profiles

I am running an application with SpringBoot 2.1.1.RELEASE.
I have a yml file with list of elements configured in the default profile and also in a "local" profile
listOfSimpleObjects:
one: oneOne, oneTwo
three: nzerjpeojr
listOfObjects:
- id: idOne
name: nameOne
---
spring:
profiles: local
listOfSimpleObjects:
two: twoOne,twoTwo
listOfObjects:
- id: idTwo
name: nameTwo
I want to map that configuration into a properties file whose definition is
#ConfigurationProperties
public class MyProperties {
private Map<String, List<String>> listOfSimpleObjects = new HashMap<String, List<String>>();
private List<SubConfig> listOfObjects = new ArrayList<>();
public Map<String, List<String>> getListOfSimpleObjects() {
return listOfSimpleObjects;
}
public void setListOfSimpleObjects(Map<String, List<String>> listOfSimpleObjects) {
this.listOfSimpleObjects = listOfSimpleObjects;
}
public List<SubConfig> getListOfObjects() {
return listOfObjects;
}
public void setListOfObjects(List<SubConfig> listOfObjects) {
this.listOfObjects = listOfObjects;
}
}
public class SubConfig {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Running with the profile "local" I was expecting to have a MyProperties object with three elements in the listOfSimpleObjects and two in the listOfObjects but it is not the case.
Below a Junit test that tells me that there is only one element in the listOfObjects.
#RunWith(SpringRunner.class)
#ActiveProfiles("local")
#SpringBootTest
public class MyPropertiesTest {
#Autowired
private MyProperties props;
#Test
public void testOnListOfStrings() {
// this assertion is ok :)
assertThat(props.getListOfSimpleObjects()).hasSize(3);
}
#Test
public void testOnListOfObjects() {
// this assertion fails :(
assertThat(props.getListOfObjects()).hasSize(2);
}
}
I asked a colleague of mine who that it was all about the key of the elements as the yml file is at first represented in a big HashMap.
So I guess there is no real answer to the question I could ask, but anyway:
is there any way to have a merge version of the listOfObject ?
Could SpringBoot be enhanced in order to support such feature (ie in case of detection of a list of items the merge is possible)
Thanks for any kind of answer :)
is there any way to have a merge version of the listOfObject ?
Out of the box, no as the documentation states :
When lists are configured in more than one place, overriding works by
replacing the entire list.
About :
Could SpringBoot be enhanced in order to support such feature (ie in
case of detection of a list of items the merge is possible)
You can open an issue/request on the Spring Boot Git.
And you guessed it works for Map as the doc states :
For Map properties, you can bind with property values drawn from
multiple sources. However, for the same property in multiple sources,
the one with the highest priority is used.
As simple and limited workaround (it works with only one specific profile. With two you will still have the overriding issue) you could specify a new property name for the list in the yaml of the specific profile.
So you would have two lists but it doesn't matter as finally you can merge it when the bean was completely loaded from the #PostConstruct annotated method.
Sample :
private List<SubConfig> listOfObjects = new ArrayList<>();
private List<SubConfig> listOfObjectsFromProfile = new ArrayList<>();
//... getters and setters
#PostConstruct
public void mergeList() {
listOfObjects.addAll(listOfObjectsFromProfile);
}

Relationship Exists in neo4j but not in Spring #NodeEntity

I have a class in my domain called Activity that looks like the following
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#NodeEntity
public class Activity {
#GraphId
private Long id;
private String title;
private String description;
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
public Activity() {
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Collection<Activity> getRelatedActivities() {
System.out.println("getting relatedActivities");
System.out.println(relatedActivities);
return relatedActivities;
}
public void addRelatedActivity(Activity activity) {
this.relatedActivities.add(activity);
}
}
I create relationships using the following repository class:
#RepositoryRestResource(collectionResourceRel = "relationships", path = "relationships")
public interface RelationshipRepository extends GraphRepository<Relationship> {
#Query("MATCH (a1:Activity), (a2:Activity) " +
"WHERE a1.title = {0} AND a2.title = {1}" +
"CREATE (a1)-[:RELATED_TO]->(a2)")
void addRelationship(String a1Title, String a2Title);
}
I have verified that this code works using the neo4j browser, which lets me see existing nodes and relationships between them. However, when I access getRelatedActivities() on an Activity object, it's always an empty array, even if that Activity has other Activity nodes related to it, clearly visible in neo4j.
How can I get the relatedActivites on an Activity to automatically populate based on its relationships correctly?
The problem in your code is that you define the "target" as an Activity here
#Relationship(type = "RELATED_TO", direction = Relationship.UNDIRECTED)
private List<Activity> relatedActivities = new ArrayList<>();
but you also have a RelationshipEntity class in your code base: Relationship with the same type RELATED_TO.
When OGM gets the result it tries to match every field but since it converts the relationship type RELATED_TO to the RelationshipEntity and not an Activity object, it does not fill the list in the Activity class.

Caching with Spring and ehcache doesnt work as expected

I have a Product model object like this -
class ProductDTO {
int id;
String code;
String description;
//getters and setters go here
}
I am writing a service (code below) that looks up products by id or code and returns their description. I am using Spring 4 and ehcache to cache the results.
I have 2 methods - one for lookup by id and one for lookup by code - they are getProductByCode and getProductById. Both return the description as a string. They do so by calling getAllProducts() which returns a list of all products. The callers then search the list for a product matching the id or code and return the description.
getAllProducts() also calls 2 methods with #CachePut for each product - to save the description Strings in cache - by key code and id.
Caching works properly if the same arguments are passed for code or id to to the getProductByCode and getProductById methods. But if I pass a different argument, getAllProducts() is called again.
How do I achieve the desired behavior - where every time a call is made to getAllProducts(), all descriptions get cached and a subsequent call looks up the cache rather than going to the repository?
public class ProductServiceImpl implements ProductService {
#Autowired
ProductsRepository ProductRepo;
#Override
public List<ProductDTO> getAllProducts() {
List<ProductDTO> products = ProductRepo.getAllProducts();
for(ProductDTO prodDTO : products) {
String desc = prodDTO.getDescription();
String code = prodDTO.getCode();
int id = prodDTO.getId();
putDescriptionInCache(desc, code);
putDescriptionInCache(desc, id);
}
return products;
}
#CachePut(value = "products", key = "#id")
public String putDescriptionInCache(String description, int id){
return description;
}
#CachePut(value = "products", key = "#code")
public String putDescriptionInCache(String description, String code){
return description;
}
#Override
#Cacheable(value="products", key="#id")
public String getProductById(Integer id) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
int currId = currDTO.getId();
if(id.equals(new Integer(currId))) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
#Override
#Cacheable(value="products", key="#code")
public String getProductByCode(String code) throws NullPointerException {
String dtoDesc = null;
List<ProductDTO> products = getAllProducts();
for(ProductDTO currDTO : products) {
String currCode = currDTO.getCode();
if(currCode.equals(code)) {
dtoDesc = currDTO.getDescription();
}
}
return dtoDesc;
}
}
As it was commented by M. Deinum, the problem comes from the annotations, like CachePut or Cacheable, being transformed into an aspect at runtime. And the main limitation with that approach is that calls from the same class are not properly captured.
As you replied yourself in the comments section, moving the annotated methods to another type that is injected in the current one solves the problem.

How to Execute a Database Call after sending a HTTP Response with Spring

so i basically want to process a HTTP-Post with a Controller in Spring and send back a result for the User AND after that i want to make a database call.
So here is my example:
#Controller
public class AngebotController extends WebMvcConfigurerAdapter {
#Autowired
private DatabaseUtils dbUtil;
#Autowired
private MyMailSender mailSender;
#RequestMapping(value = REQUEST_PATH, method = RequestMethod.POST)
public String doPost(#Valid FormInput input, BindingResult bindingResult) {
// .. some input validations here
// after the validation is complete i will have accesss to a object, that i just created, just like the following
// Lets say that this object holds important values for the database query
final MyObject validatedInput = new MyObject();
// Start a new Thread to do the remaining work (the Database Call)
Thread t = new Thread() {
#Override
public void run() {
// so i am starting the database query with the information that i just validated above
// That Database Query will Return a List of Items based on the given MyObject
// This Query will take a long time and i dont want the user to wait, because this data is not nessessary for the user
List<Item> items = dbUtil.getStuffByInput(validatedInput);
for(Item i : items) {
// Now i just want to send some informations about the item via email, this part works
mailSender.sendMail("mail#mail.mail", i);
}
}
}.start();
return "viewname";
}
}
#Service
public class DatabaseUtils {
#Autowired
private ItemRepository repository;
public List<Item> getStuffByInput(MyObject o) {
List<Item> items = repository.findAllByMyObject(o);
// Doing some more stuff with those items here ..
return items;
}
}
// The Implementation will be generated by Spring
public interface ItemRepository extends CrudRepository<Item, Long> {
// will select all Items by comparing the myObject with each Item
// This also works like intended
public List<Item> findAllByMyObject(MyObject myObject);
}
So where is my Problem?
The only Problem i have is, that the Database Query will end throwing an Exception, because the Database Connection was closed (i guess by Spring)
The Exception: Exception in thread "Thread-6" org.hibernate.SessionException: Session is closed!
Any Help appreciated.
Thanks!

Resources