I am getting started to use Spring Data Elasticsearch.
I read:
One of the attributes of the class needs to be an id, either by
annotating it with #Id or using one of the automatically found names
id or documentId.
but when I marked my Project entity field projectId with #Id then elasticsearch still is saying:
No id property found for class com.example.domain.entity.Project!
I figured out that I am using annotation #Id from JPA package: javax.persistence.Id. When I add another #Id annotation #org.springframework.data.annotation.Id for my field then fetching from repository is working!
The issue is that I do not want to use 2 kind of #Id annotation at the same time. Moreover, I would like to use JPA annotation only because of other module is using JPA based repository layer (Spring Data JPA).
Does Spring Data Elasticsearch supports #Id annotation from JPA? It is very important to know because further what about embedded id? Does #EmbeddedId annotation is supported by Spring Data Elasticsearch?
My entity:
#Entity
#Document(indexName = "project_list", type = "external")
public class Project implements Serializable {
#Id
#org.springframework.data.annotation.Id <-- without it Spring Data Elasticsearch is complaining that 'No id property found'
#Column(name = "PROJECT_ID")
private Long projectId;
.... other fields and getters/setters
}
Yes, 1.3.0 does support #Id but you need a getter (maybe a bug?)
ElasticsearchTemplate.getPersistentEntityId takes your entity, tries to find the annotation #Id then returns the value of the id only if there is a getter defined.
However It doesn't seem to support #EmbeddedId: SimpleElasticsearchPersistentProperty.SUPPORTED_ID_PROPERTY_NAMES
I have a similar issue, I am also using JPA and Elastic search both and it resolved after changing
#Column(name = "PROJECT_ID")
private Long projectId;
to
javax.persistence.Id;
the default name of column id
#Column(name = "id")
private Long id;
Related
In Spring Data Elasticsearch is there a way of detecting that the mapping on the index does not match the mapping created from the Entity object?
i.e.
I allowed Spring Data Elasticsearch to create the mapping originally from the entity model annotated with #Document and #Field
At a later point I add a new field to the model, but do not update the index mapping, this new field then won't be correctly configured until I re-index to the new mapping
So is there a way to detect such discrepancies so I will know which indexes need to have their mappings re-created, and documents re-indexed?
This is an interesting question, but the answer is not too complicated.
Let's assume we have a Foo entity class to be stored in an foo index and a FooRepository that uses this class. On
application startup, when the index does not exist, it will be created with the mapping derived from the entity.
In order to detect changes in the maping derived from the class and the one stored in Elasticsearch you can use an
approach like this:
#Component
public class FooMappingValidator {
private static final Logger LOGGER = LoggerFactory.getLogger(FooMappingValidator.class);
private final ObjectMapper objectMapper = new ObjectMapper();
private final ElasticsearchOperations operations;
public FooMappingValidator(ElasticsearchOperations operations) {
this.operations = operations;
}
#Autowired
public void checkFooMapping() {
var indexOperations = operations.indexOps(Foo.class);
if (indexOperations.exists()) {
LOGGER.info("checking if mapping for Foo changed");
var mappingFromEntity = indexOperations.createMapping();
var mappingFromEntityNode = objectMapper.valueToTree(mappingFromEntity);
var mappingFromIndexNode = objectMapper.valueToTree(indexOperations.getMapping());
if (!mappingFromEntityNode.equals(mappingFromIndexNode)) {
LOGGER.info("mapping for class Foo changed!");
indexOperations.putMapping(mappingFromEntity);
}
}
}
}
This is a Spring component that has ElasticsearchOperations injected and that has a method that is annotated with
#Autowired. An autowired method is executed after all the dependencies have been injected in the beans. This means
it runs before the normal application logic is started.
In this method we first get an IndexOperations instance for our entity class. Next we check if the index exists,
if it doesn't, we do not need to check.
In the next step we get the current mapping from the entity and convert it to a JsonNode and do the same with the
mapping we retrieve from Elasticsearch. We use JsonNodes here because the have an equals() method that
does the comparison we need.
If we detect that the both mappings are different, we write the new one to the index with the putMapping() method.
NOTE:
This only works when new properties are added to the entity, as existing mappings cannot be changed in Elasticsearch,
there you'd need reindexing.
I have the following relationship entity for neo4j Graph Model using Spring data neo4j 6.1.1 to represent relationship like Person-BookedFor->Movie where i can use UUID string for node repositories (Person, Movie) but not for the following relationship Entity BookedFor.
Note: since the neo4j doc describes this neo4j doc ref
public interface BookedForRepository extends Neo4jRepository<BookedFor, String> {}
#RelationshipProperties
public class BookedFor {
#Id
#GeneratedValue(UUIDStringGenerator.class)
public String rid;
#Property
private Date bookedDate;
}
which throws error as below:
The target class *.entities.BookedFor for the properties of the relationship BookedFor is missing a property for the generated, internal ID (#Id #GeneratedValue Long id) which is needed for safely updating properties
Note: If i use like following, it creates relationship with the internal id of neo4j
public interface BookedForRepository extends Neo4jRepository<BookedFor, Long> {}
#RelationshipProperties
public class BookedFor {
#Id
#GeneratedValue
public Long id;
#Property
private Date bookedDate;
}
but this will create uncertainty on data migration / data mutation as we rely on internal id of neo4j relationship entity. Please correct me if i'm wrong.
spring data neo4j doc ref
Could anyone please help to proceed with this in a better way?
Also, please comment if more clarification / details required.
You cannot access relationship properties directly via repositories.
Those classes are just an encapsulation for properties on relationships and are not meant to represent a "physical" relationship or more a relationship entity.
Repositories are for #Node annotated classes solely.
If you want to access and modify the properties of a relationship, you have to fetch the relationship defining entity.
A relationship on its own is always represented by its start and end node.
The lately introduced required #Id is for internal purposes only.
If you have a special need to persist an id-like property on the relationship, it would be just another property in the #RelationshipProperties annotated class.
I have a "strange" behavior with list properties of any kind of entity.
When I try to set a non empty list to a retrieved entity that has a list value with null (because is not fetched) the entity doesn't "reflect" the new non-empty values that I set.
Steps
Retrieve any entity (ex. user) from database in an ejb with criteria.
The retrieved entity has a OneToMany attribute (roles) by default is lazy that's why if I don't do a fetch (I don't) the list is null
Then try to fill the role list with a NON-EMPTY list
Here the list that I set has values but the user list has a null in the roles list attribute even though I set it in the step 3
I don't know why this is happening, the only way I could fix this is cloning (using SerializationUtils from Apache commons.lang3) the user entity (see the steps) and with this work, but I don't know why.
Retrieve any entity (ex. user) from database in an ejb with criteria.
Clone retrieved entity and assign to new one User clonedUser = SerializationUtils.clone(originalRetrivedUser);
Then try to fill the role list with a NON-EMPTY list to the clonedUser
The list of clonedUser has the correct values
Why do I need to clone? Why can't I set a list to the entity if is not cloned?
I'm using Apache TomEE 1.6.0-SNAPSHOT (with the openjpa version embedded)
***** ENTITIES *****
#Entity
public class User implements Serializable{
#Id
private int id;
private String userName;
private String password;
#OneToMany(mappedBy = "user")
private List<Role> roles;
//getters and setters..
}
#Entity
public class Role implements Serializable{
#Id
private int id;
private String roleName;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
//getters and setters..
}
**** EJB INTERFACE LOCAL ****
#Local
public interface MyEJBLocal{
public User getUserWithRoles();
}
**** EJB CLASS ****
#Stateless
public class MyEJB implements MyEJBLocal{
#PersistenceContext(unitName ="ANY_NAME")
private EntityManager em;
#Override
public User getUserWithRoles(){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.where(cb.equal(root.get(User_.userName),"john"));
User userJohn = em.createQuery(cq).getSingleResult(); // if I want this work I have to do User userJohn = SerializationUtils.clone(em.createQuery(cq).getSingleResult()); [1*]
//I will create a list of role just for try to set any role the userJohn
List<Role> roleList = new ArrayList<Role>(2);
roleList.add(new Role(1,'ADMIN'); //creating and adding role 1
roleList.add(new Role(2,'DEVELOPER');//creating and adding role 2
//setting the list of roles created to the user, as you can see the list has 2 values but the value roles of userJohn always is set to null, my setters and getters are correct
userJohn.setRoles(roleList);
return userJohn;
}
}
*** MANAGED BEAN ****
#Named
public class MyBean implements Serializable{
#EJB
private MyEJBLocal ejb;
public void anyMethod(){
User user = ejb.getUserWithRoles();
user.getRoles(); //<---- HERE THE LIST IS ALWAYS NULL but if I use clone in the ejb method (see 1*) I can get the corrected values.
}
}
I know I can get the values by fetching but I'm trying not to do it.
In the previous example I tried to set the list in the ejb, but if I remove and then put the list in the managed bean I get the same behavior
I just want want to retrieve an entity and add some info but I don't want to persist that new info.
I'm trying to use the detach method but nothing changes.
I don't want to use #Transient because sometimes I need to persist the data, the idea is easy, I want to retrieve an entity then in the managedbean I want to add a new list but no matter what I set to the list (roles) of the entity (user) the list on the entity always set to null like if the list in the entity is protected in some way.
The #Query on the property retrieves the values only if I retrieve the entity from the DB.
#NodeEntity
public class Team
{
#GraphId
private Long nodeId;
#RelatedTo (type = "PREVIOUSLY_KNOWN_AS")
private Team previouslyKnownAs;
#Query ("START t=node({self}) MATCH t-[:PREVIOUSLY_KNOWN_AS]-other RETURN other")
private Iterable<Team> aliases;
}
The below test works only if I uncomment the line to read it explicitly from the db. Why is it necessary? I see the query being run after the save(t) but the alias field is null if I doesn't read it from DB by uncommenting the line
#Test
public void alias()
{
Team t = new Team();
t.setName("Alpharetta One");
Team prev = new Team();
prev.setName("Previous Name");
teamRepo.save(prev);
t.setPreviouslyKnownAs(prev);
teamRepo.save(t);
//t = teamRepo.findOne(t.getNodeId());//only works if I uncomment
assertNotNull(t.getAliases());
}
Try
t=teamRepo.save(t);
I dont think that the save operation will update the POJO which you give to it, while the returned Object should be a managed enttiy.
The key lies in the reference documentation
The #Query annotation leverages the delegation infrastructure supported by Spring Data Neo4j.It provides dynamic fields which, when accessed, return the values selected by the provided query language expression.
Since it is a dynamic field, the value isnt instanciated but instead fetched from the DB every time the get method is called. To do this, a proxy object has to be used. However there is no way for SDN to change your t Object reference to the proxy object, and thats why its not working, if you are not using the entity returned by save().
In one of my pojo i have used
#RelatedTo(type = "User")
#Transient
private Set<User> users = new HashSet<User>();
which is not mapped with Hibernate, to make hibernate ignore this i have used #Transient, otherwise i am getting Unknown field users but now neo4j is also ignoring the field. It is not storing the relationship. If i remove #Transient neo4j is working fine, but Hibernate is giving execption.
How to resolve this?
Are you using the #NodeEntity annotation with the "partial" argument? This is required for cross-store setups, have a look at the manual: http://static.springsource.org/spring-data/neo4j/docs/2.2.0.RELEASE/reference/htmlsingle/#reference:cross-store