I've got something weird in my project and I can't see if it's may fault or not.
I'm using Spring with spring-data-neo4j (v4.0.0).
This code in my controller breaks relationships.
#RequestMapping("/initialize")
public Object initialize() {
Organization orga = new Organization();
orga.setName("WEAVERS");
this.organizationRepository.save(orga);
User user = new User();
user.setEmail("t.rignoux#gmail.com");
user.setFirst_name("Thierno");
user.setLast_name("Rignoux");
user.setLogin("317");
user.setOrganization(orga);
this.userRepository.save(user);
User user2 = new User();
user2.setEmail("petitenessie#gmail.com");
user2.setFirst_name("Eléonore");
user2.setLast_name("Klein");
user2.setLogin("nessie");
user2.setOrganization(orga);
this.userRepository.save(user2);
Project project = new Project();
project.setName("PROJET 1");
project.setOrganization(orga);
project.setUser(user);
this.projectRepository.save(project);
Project project2 = new Project();
project2.setName("PROJET 2");
project2.setOrganization(orga);
project2.setUser(user2);
this.projectRepository.save(project2);
return orga;
}
As you can see on this graph:
The relation between ORGANIZATION and USER1 has been severed when I created USER2.
Broken relationships is something I see everywhere in my application... I don't understand!
neo4j configuration
#EnableTransactionManagement
#Import(RepositoryRestMvcConfiguration.class)
#EnableScheduling
#EnableAutoConfiguration
#ComponentScan(basePackages = {"fr.weavers.loom"})
#Configuration
#EnableNeo4jRepositories(basePackages = "fr.weavers.loom.repositories")
public class LoomNeo4jConfiguration extends Neo4jConfiguration {
public static final String URL = System.getenv("NEO4J_URL") != null ? System.getenv("NEO4J_URL") : "http://localhost:7474";
#Override
public Neo4jServer neo4jServer() {
return new RemoteServer(URL,"neo4j","loomREST2016");
}
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("fr.weavers.loom.domain");
}
}
Organization Domain :
public class Organization extends Node {
#GraphId
Long id;
String name;
#Relationship(type = "SET_UP", direction = Relationship.OUTGOING)
Set<SET_UP> projects;
#Relationship(type = "WORK_FOR", direction = Relationship.INCOMING)
Set<WORK_FOR> users;
[...]
WORK_FOR Domain :
public class WORK_FOR extends Relationship {
#GraphId
private Long relationshipId;
#StartNode
public User user;
#EndNode
public Organization organization;
public WORK_FOR() {
super();
}
}
User Domain:
public class User extends Node {
#GraphId
Long id;
String login;
#JsonIgnore
String password;
String first_name;
String last_name;
String email;
#Relationship(type = "WORK_FOR")
WORK_FOR workFor;
I looked everywhere and I can't find anything to help me...
Thank you
In your initialisation request you make several repository calls to construct the database. Each of these calls will construct a new Session, effectively clearing out information from the previous one because the Spring config defaults to Session-per-request. This is most likely the reason existing relationships are being deleted.
If you add the following bean definition to your config, I believe the problem should be resolved.
#Override
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public Session getSession() throws Exception {
return super.getSession();
}
Please refer to the documentation here for more information about Session scope.
Please annotate the incoming relationship on your Node class:
#Relationship(type = "WORK_FOR", Direction = Relationship.INCOMING)
WORK_FOR workFor;
As per the documentation, all INCOMING relationships must be fully annotated - the default is OUTGOING.
Related
I´m currently trying to implement multi-tenancy into my JHipster microservices. However, I can't find a way to implement tenant-based routing for elasticsearch.
So far I have managed to implement datasource routing for the PostgreSQL DBs similar to the following article: https://websparrow.org/spring/spring-boot-dynamic-datasource-routing-using-abstractroutingdatasource
When I started looking for ways to implement multi tenancy in elasticsearch, I found the following article: https://techblog.bozho.net/elasticsearch-multitenancy-with-routing/
There I read about tenant-based routing. First I tried looking it up on the internet, but anything I found was either over 5 years old or not related to java, much less to Spring/Jhipster. Then I tried looking into the methods of ElasticSearchTemplate, the annotation variables of #Document and #Settings and the configuration options in the .yml file, but didn't find anything useful.
I'm currently using Jhipster version 7.9.3, which uses the Spring-Boot version 2.7.3. All the microservices were created with JDL and on half of them I put elasticsearch into the configuration. The other half does not matter.
Edit: I want to add that multi-tenancy in my database is archived by database separation(Tenant1 uses DB1, Tenant2 uses DB2 etc.). The tenant variable is an enum and not included in my entities.
Edit2: I implemented my own solution. I use the tenants as indexes and use my ContextHolder from DataSource Routing to route to the correct tenant index. For that I had to do some changes the elasticsearchTemplate in the generated classes of the package "<my.package.name>.repository.search".
It might not be the most efficient way to reach multi tenancy with elasticsearch, but it doesn't need much configuration.
Here is the code:
public interface ProductSearchRepository extends ElasticsearchRepository<Product, Long>, ProductSearchRepositoryInternal {}
interface ProductSearchRepositoryInternal {
Stream<Product> search(String query);
Stream<Product> search(Query query);
void index(Product entity);
}
class ProductSearchRepositoryInternalImpl implements ProductSearchRepositoryInternal {
private final ElasticsearchRestTemplate elasticsearchTemplate;
private final ProductRepository repository;
ProductSearchRepositoryInternalImpl(ElasticsearchRestTemplate elasticsearchTemplate, ProductRepository repository) {
this.elasticsearchTemplate = elasticsearchTemplate;
this.repository = repository;
}
#Override
public Stream<Product> search(String query) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQuery(query));
return search(nativeSearchQuery);
}
#Override
public Stream<Product> search(Query query) {
return elasticsearchTemplate.search(query, Product.class, IndexCoordinates.of(TenantContextHolder.getTenantContext().getTenant())).map(SearchHit::getContent).stream();
}
#Override
public void index(Product entity) {
repository.findById(entity.getId()).ifPresent(t -> elasticsearchTemplate.save(t, IndexCoordinates.of(TenantContextHolder.getTenantContext().getTenant())));
}
}
Edit3: Since people might not know where ".getTenant()" comes from, I'll show my tenant enumeration:
public enum Tenant {
TENANTA("tenant_a"),
TENANTB("tenant_b");
String tenant;
Tenant(String name) {
this.tenant=name;
}
public String getTenant() {
return this.tenant;
}
}
Edit4: My solution is not working as planned. I will give an update once I found a better and more robust solution.
Edit5: I have found out how to implement tenant-based routing. First you have to add the following Annotation to your entities:
#org.springframework.data.elasticsearch.annotations.Routing(value = "tenant")
In my case I had to include the enum "Tenant" into my entities along with the getter and setter:
#Transient
private Tenant tenant;
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
Then I have to set the tenant during the processing of a REST request before it gets indexed by elasticsearchtemplate:
entity.setTenant(TenantContextHolder.getTenantContext());
As for the search function, I had to add a term query as a filter to enable routing:
#Override
public Stream<Product> search(String query) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQuery(query)
, QueryBuilders.termQuery("_routing", TenantContextHolder.getTenantContext()));
return search(nativeSearchQuery);
}
The method "setRoute(String route)" of "nativeSearchQuery" either does not work in my case or I didn't understand how it works.
I have successfully tested this implementation with GET and POST requests. Currently I have a problem with elasticsearch overwriting data if the id of the entity from one tenant I want to save is the same id as another entity with a different tenant.
After some trial and error, I found a solution to the overwriting problem and successfully completed and tested my implementation of tenant-based routing. Here is the code:
Product.java
import java.io.Serializable;
import javax.persistence.*;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.springframework.data.elasticsearch.annotations.Field;
#Entity
#Table(name = "product")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
#org.springframework.data.elasticsearch.annotations.Document(indexName = "product")
#SuppressWarnings("common-java:DuplicatedBlocks")
#org.springframework.data.elasticsearch.annotations.Routing(value = "tenant")
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Transient
private Tenant tenant;
#Transient
#Field(name = "elastic_id")
#org.springframework.data.annotation.Id
private String elasticsearchId;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator =
"sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
#Field("postgres_id")
private Long id;
//Getters, Setters and other variables
}
ProductSearchRepository
public interface ProductSearchRepository extends ElasticsearchRepository<Product, Long>, ProductSearchRepositoryInternal {}
interface ProductSearchRepositoryInternal {
Stream<Product> search(String query);
Stream<Product> search(Query query);
void index(Product entity);
Product save(Product entity);
void deleteById(Long id);
}
#Transactional
class ProductSearchRepositoryInternalImpl implements ProductSearchRepositoryInternal {
private final ElasticsearchRestTemplate elasticsearchTemplate;
private final ProductRepository repository;
ProductSearchRepositoryInternalImpl(ElasticsearchRestTemplate elasticsearchTemplate, ProductRepository repository) {
this.elasticsearchTemplate = elasticsearchTemplate;
this.repository = repository;
}
#Override
public Stream<Product> search(String query) {
NativeSearchQuery nativeSearchQuery = new NativeSearchQuery(queryStringQuery(query)
, QueryBuilders.termQuery("_routing", TenantContextHolder.getTenantContext()));
nativeSearchQuery.setMaxResults(30);
return search(nativeSearchQuery);
}
#Override
public Stream<Product> search(Query query) {
return elasticsearchTemplate.search(query, Product.class).map(SearchHit::getContent).stream();
}
#Override
public void index(Product entity) {
entity.setTenant(TenantContextHolder.getTenantContext());
repository.findById(Long.valueOf(entity.getId())).ifPresent(t -> {
entity.setElasticsearchId(entity.getTenant()+String.valueOf(entity.getId()));
elasticsearchTemplate.save(t);
});
}
#Override
public Product save(Product entity) {
entity.setTenant(TenantContextHolder.getTenantContext());
entity.setElasticsearchId(entity.getTenant()+String.valueOf(entity.getId()));
return elasticsearchTemplate.save(entity);
}
#Override
public void deleteById(Long id) {
elasticsearchTemplate.delete(TenantContextHolder.getTenantContext() + String.valueOf(id), Product.class);
}
}
I am having below mappings as Company having 1:N relation with CompanyFunds
#Entity
public class Company{
#Id
private Integer companyId;
private String name;
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
}
#Entity
public class CompanyFunds{
#Id
private Integer fundId;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "company_id")
private Company company;
}
I am using Spring-data-Jpa for my persistence layer and below are the controller and service methods:
//controller
#GetMapping(value = "/{companyId}")
public Resource<Company> find(#PathVariable Integer companyId) {
Resource<Company> companyResource = companyService.find(companyId);
return companyResource;
}
//service
public Resource<CompanyTypeOther> find(Integer companyId) {
Company company = companyRepository.findById(companyId);
return restResourceAssembler.toResource(company);
}
#Component
public class RestResourceAssembler implements ResourceAssembler<T, Resource<T>> {
private EntityLinks entityLinks;
public RestResourceAssembler(EntityLinks entityLinks) {
this.entityLinks = entityLinks;
}
#Override
public Resource<T> toResource(T entity) {
Resource<T> resource = new Resource<>(entity);
resource.add(entityLinks.linkToSingleResource(entity.getClass(), entity.getId()).withSelfRel());
return resource;
}
}
Now the weird thing is, until the return companyResource;(in controller) doesn't get executed, the companyResource contains null for companyFunds i.e the LAZY loading is working fine till that point. But the moment the return companyResource; executes, something goes inside the Spring and the Select statement for CompanyFund gets fired. I debugged the steps and below is the code(try block) responsible for this:
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
......
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
........other code
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
.....
}
There are no toString() declared in entities, also no getCompanyFund() called. Can't understand what Spring is doing with returnValue above, so that some getters(or something) are getting called.
One more thing I noticed is, this problem only occurs when the Resource<Company> is returned. If I return Company from the controller, nothing unexpected happens. Lazy loading works fine.
Since I want to lazy load the entity, a little fix/hack solved the problem for me(as of now).
#JsonIgnore
#OneToMany(mappedBy = "company")
private List<CompanyFund> companyFunds;
#JsonIgnore prevents LAZY loaded entity from being serialized. So I guess the jackson is the culprit here.
This is not a fix is my view but just a hack to do the thing. Still waiting someone from Spring team to reply.
I'm using Spring Boot for a project, I'm stuck with lazy loading.
What I want to do is load data in my controller, then send to presentable object, that will extract needed information and the JSON serializer do the bad work to create my custom HTTP response.
the problem occurs when the UserPresentation class calls the folder getter, the error is the well known: could not initialize proxy - no Session.
Of course the default fetch is LAZY for the folder and I want this, but I don't know how to prepare the object to be usable in the Presentation.
I copy-pasted only Folder set to be clear and short, but I've more collection inside User class, all of them give me the same problem.
I know that I could call getter in controller just to initialize Collections, but I find this like an hardcoding, in fact if I want add something to presentable I need to do in controller too.
I've tried too with #Transactional but not works.
Here are my class:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "USER_ID")
private Integer id;
#Column(unique = true)
private String email;
private String password;
#Enumerated(EnumType.STRING)
private Authority userAuthority;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL)
private Set<Folder> ownFolders = new HashSet<>();
... getter setter
}
#RestController
public class UserController {
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
}
public Integer getId() {
return user.getId();
}
public String getEmail() {
return user.getUsername();
}
public String getAuthority() {
return user.getUserAuthority().name();
}
public boolean isEnabled() {
return user.isEnabled();
}
public Integer getOwnFolders() {
Set<Folder> folderList = user.getOwnFolders();
if (folderList == null)
return 0;
return folderList.size();
}
}
Last two just to be clear
public class ResponseManager {
// DATA
public static ResponseEntity respondData(Presentable presentable, String token) {
CustomResponse response = new DataResponse<>(presentable);
return new ResponseEntity<>(response, HttpStatus.OK);
}
}
public class DataResponse<T extends Presentable> extends CustomResponse {
private T data;
public T getData() {
return data;
}
private void setData(T data) {
this.data = data;
}
public DataResponse(T data) {
this.setData(data);
}
#Override
public String getType() {
return DATA;
}
}
I suppose you load the current user form the database with:
User currentUser = loginService.getCurrentUser();
and the getCurrentUser() method is transactional. You can either:
Use JPQL like this:
"select u from User u join fetch u.ownFolders where ... " to load the user's info (this way ownFolders relation is eagerly fetched)
or
Simply call user.getOwnFolders() inside getCurrentUser() to trigger
the fetch.
I found a way, even is a little bit dirty it allows me to do what I want without big change at the code.
Practically the problem occurs during the JSON serialization, that run outside of my control (somewhere inside Spring classes just before send HTTP response), so I manually serialized every Presentable object inside a #Transactional block just after its creation.
These are the changed classes:
public class UserPresentation implements Presentable {
private User user;
public UserPresentation(User user) {
this.user = user;
this.initialize() //ADDED (called here and in every other class that implements Presentable)
}
...getter and setter (which I want as JSON fields)
}
#RestController
public class UserController {
#Transactional //ADDED
#GetMapping(value = "/api/user", produces = APPLICATION_JSON_VALUE)
public CustomResponseEntity userInfo() {
User currentUser = loginService.getCurrentUser();
UserPresentation userPresentation = new UserPresentation(currentUser);
return ResponseManager.respondData(userPresentation);
}
}
Before this fix, the interface was used only to use Polymorfism inside ResponseManager, so was empty
public interface Presentable {
default void initialize() {
try {
new ObjectMapper().writeValueAsString(this);
} catch (JsonProcessingException e) {
throw new RuntimeJsonMappingException(e.getMessage());
}
}
}
I would suggest you use https://github.com/FasterXML/jackson-datatype-hibernate
The module supports datatypes of Hibernate versions 3.x , 4.x and 5.x; as well as some of the associated behavior such as lazy-loading and detection of transiency (#Transient annotation).
It knows how to handle Lazy loading after the session is closed , it will skip the json conversion for objects marked as Lazy fetch when outside session
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5</artifactId>
<version>2.9.8</version>
</dependency>
ObjectMapper mapper = new ObjectMapper();
// for Hibernate 4.x:
mapper.registerModule(new Hibernate4Module());
// or, for Hibernate 5.x
mapper.registerModule(new Hibernate5Module());
// or, for Hibernate 3.6
mapper.registerModule(new Hibernate3Module());
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/*
* Here we register the Hibernate4Module into an ObjectMapper, then set this * custom-configured ObjectMapper to the MessageConverter and return it to be * added to the HttpMessageConverters of our application
*/
public MappingJackson2HttpMessageConverter jacksonMessageConverter() {
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper hibernateAwareObjectMapper = new ObjectMapper();
hibernateAwareObjectMapper.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS);
hibernateAwareObjectMapper.enable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
// Registering Hibernate5Module to support lazy objects
hibernateAwareObjectMapper.registerModule(new Hibernate5Module());
messageConverter.setObjectMapper(hibernateAwareObjectMapper);
return messageConverter;
}
}
XML config
<mvc:annotation-driven>
<mvc:message-converters>
<!-- Use the HibernateAware mapper instead of the default -->
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="path.to.your.HibernateAwareObjectMapper" />
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
I'm quite new in spring boots so I hope this is not a silly question
I have a #Service that needs to initiate a class attribute, this attribute needs a information that comes from the RestPayload in the Controller. I'm not finding the most recommend way to do that.
#RestController
public class UserController {
#Autowired
private UserService userService;
#RequestMapping("/searchUser")
public List<UserWrapper> searchUser(#RequestBody UserWrapper userWrapper) {
List<UserWrapper> returnUserWrapper = userService.findByName(userWrapper);
return returnUserWrapper;
}
}
And the service layer, I would like to be something like:
#Service
public class UserService {
private LdapTemplate ldapTemplate;
public static final String BASE_DN = "xxxxxxx";
#Value( value = "${sample.ldap.url}" )
private String ldapUrl;
#Value( value = "${sample.ldap.base}" )
private String ldapBase;
public UserService() {
}
public UserService(String dn, String password) {
LdapContextSource ctxSrc = new LdapContextSource();
System.out.println(this.ldapUrl);
ctxSrc.setUrl(ldapUrl);
ctxSrc.setBase(ldapBase);
ctxSrc.setUserDn(dn);
ctxSrc.setPassword(password);
ctxSrc.afterPropertiesSet(); // this method should be called.\
this.ldapTemplate = new LdapTemplate(ctxSrc);
}
The String dn and String password will come in the REST Payload but the other properties comes from a properties file.
Hope someone can guide me with best practices
for ldap authentication you should have a look on spring-security:
https://docs.spring.io/spring-security/site/docs/current/reference/html5/#ldap
on the other hand you can access almost any request parameter by just injecting it via a annotation like in these examples:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-requestheader
I'm experimenting with the old Petclinic example and I noticed that the vets ArrayList in the SimpleJdbcClinic exists for the life of the session. It seems like it should exist only for the request since I don't see any annotations putting it into the session context. Could someone point out what I'm failing to understand?
Here is the vets class:
#XmlRootElement
public class Vets {
private List<Vet> vets;
#XmlElement
public List<Vet> getVetList() {
if (vets == null) {
vets = new ArrayList<Vet>();
}
return vets;
}
}
The service:
#Service
#ManagedResource("petclinic:type=Clinic")
public class SimpleJdbcClinic implements Clinic, SimpleJdbcClinicMBean {
private SimpleJdbcTemplate simpleJdbcTemplate;
private SimpleJdbcInsert insertOwner;
private SimpleJdbcInsert insertPet;
private SimpleJdbcInsert insertVisit;
private final List<Vet> vets = new ArrayList<Vet>();
:
:
#Transactional(readOnly = true)
public Collection<Vet> getVets() throws DataAccessException {
synchronized (this.vets) {
if (this.vets.isEmpty()) {
refreshVetsCache();
}
return this.vets;
}
}
}
The controller mapping:
#RequestMapping("/vets")
public ModelMap vetsHandler() {
Vets vets = new Vets();
vets.getVetList().addAll(this.clinic.getVets());
return new ModelMap(vets);
}
Once the vets list is created it survives multiple requests.
Thanks
I think it avoids redundant database calls by storing all vets in the private final List<Vet> vets. Also vets variable is a property of a singleton #Service SimpleJdbcClinic