spring-data-mongodb aggregation with composite ID - spring

I'm having trouble reading documents from MongoDB using the aggregation framework: I always get null IDs in my results. This only happens for documents that have composite IDs. I tried various versions of spring-data-mongodb (1.10.12, 2.0.7), same result.
Entity definition class
#Document(collection="entities")
public class MyEntity {
static class CompositeKey implements Serializable {
private String stringKey;
private Integer intKey;
public CompositeKey(String stringKey, Integer intKey) {
this.stringKey = stringKey;
this.intKey = intKey;
}
public Integer getIntKey() {
return intKey;
}
public String getStringKey() {
return stringKey;
}
public String toString() {
return "{" + stringKey + " - " + intKey + "}";
}
}
#Id
private CompositeKey id;
private String param;
public MyEntity() {}
public MyEntity(String stringKey, Integer intKey) {
id = new CompositeKey(stringKey, intKey);
}
public CompositeKey getId(){
return id;
}
public void setId(CompositeKey id) {
this.id = id;
}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
}
Testing code
public static void main(String[] args) {
MongoClient client = new MongoClient("127.0.0.1");
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(client, "aggTest");
MongoTemplate mongoTemplate = new MongoTemplate(factory);
MyEntity entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("one", 1));
entity.setParam("param1");
mongoTemplate.save(entity);
entity = new MyEntity();
entity.setId(new MyEntity.CompositeKey("two", 2));
entity.setParam("param2");
mongoTemplate.save(entity);
Criteria crit = Criteria.where("param").ne("param3");
List<AggregationOperation> aggOpList = new ArrayList<AggregationOperation>();
aggOpList.add(Aggregation.match(crit));
System.out.println("Documents fetched with find: ");
for (MyEntity aggResult : mongoTemplate.find(new Query(crit), MyEntity.class).toArray(new MyEntity[0]))
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
System.out.println("\nDocuments fetched with aggregate: ");
TypedAggregation<MyEntity> aggregation = new TypedAggregation<>(MyEntity.class, aggOpList);
AggregationResults<MyEntity> aggregate = mongoTemplate.aggregate(aggregation, MyEntity.class);
for (MyEntity aggResult : aggregate.getMappedResults())
System.out.println(aggResult.getId() + " - " + aggResult.getParam());
}
Output
Documents fetched with find:
{one - 1} - param1
{two - 2} - param2
Documents fetched with aggregate:
null - param1
null - param2
Debugging into the following method MappingMongoConverter.read(final MongoPersistentEntity entity, final Document bson, final ObjectPath path) I found that in the first case (find method) the documentAccessor variable has the following contents
Document{{_id=Document{{stringKey=one, intKey=1}}, param=param1, _class=MyEntity}}
whereas in the second case (aggregation query) it looks like
Document{{stringKey=one, intKey=1, param=param1, _class=MyEntity}}
The document gets flattened somehow, which makes it impossible for the converter to populate the ID field. I must be doing something wrong, but what?

Spring Data MongoDB lower than 3.x automatically flatten composite id (fields under composite id are unwrapped and place at root object). This is removed in version 3.0 onwards:
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#new-features.3.0

Related

Why might Room database IDs all show as 0 in a log/Toast message?

I'm trying to get the id of the last inserted object into a database using Room with Android. I can fetch the last object using an SQL query and can call other methods to get the various properties of that object which the user has set when saving the object. But getId() always returns 0. When I examine the table contents in Android Studio's app inspector, I can clearly see that Room is generating a unique primary key for each row, but I just can't get at it. Can anyone suggest what the problem might be?
Here's the Dao query:
#Query("SELECT * FROM gamebooks_table WHERE gamebookId=gamebookId ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID();
And here's the annotated entity class:
#Entity(tableName = "gamebooks_table")
public class Gamebook {
#PrimaryKey(autoGenerate = true)
private long gamebookId;
private String gamebookName;
private String gamebookComment;
private String gamebookPublisher;
private float gamebookStarRating;
public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
this.gamebookName = gamebookName;
this.gamebookComment = gamebookComment;
this.gamebookPublisher = gamebookPublisher;
this.gamebookStarRating = gamebookStarRating;
}
public long getGamebookId() {
return gamebookId;
}
public String getGamebookName() {
return gamebookName;
}
public String getGamebookComment() {
return gamebookComment;
}
public String getGamebookPublisher() {
return gamebookPublisher;
}
public float getGamebookStarRating(){
return gamebookStarRating;
}
public void setGamebookId(long gamebookId) {
this.gamebookId = gamebookId;
}
}
SOLVED
Finally sorted this by adding an Observer to my DAO method which returns a single gamebook. Within the Observer's onChanged() method, I can loop through all Gamebooks in the LiveData List (even though there's only one because I'm limiting it to one in the SQL query) and call getId() to get their respective IDs.
mainViewModel.getSingleGamebook().observe(this, new Observer<List<Gamebook>>() {
#Override
public void onChanged(List<Gamebook> gamebooks) {
int i=0;
for(Gamebook gamebook : gamebooks){
gamebookId= gamebook.getGamebookId();
Log.d(TAG, "Gamebook Name: "+gamebook.getGamebookName()+ " Database ID: " +gamebookId);
i++;
}
}
});
I believe that your issue is due to the only constructor being available not setting the id so the LiveData uses the default value of 0 for a long.
I'd suggest having a default constructor and thus all setters/getters and (optionally) using #Ignore annotation for one of the constructors..
without #Ignore you get warnings Gamebook.java:8: warning: There are multiple good constructors and Room will pick the no-arg constructor. You can use the #Ignore annotation to eliminate unwanted constructors. public class Gamebook {
e.g. :-
#Entity(tableName = "gamebooks_table")
public class Gamebook {
#PrimaryKey(autoGenerate = true)
private long gamebookId;
private String gamebookName;
private String gamebookComment;
private String gamebookPublisher;
private float gamebookStarRating;
public Gamebook(){} /*<<<<< ADDED */
#Ignore /*<<<<< ADDED - is not required - could be on the default constructor but not both*/
public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
this.gamebookName = gamebookName;
this.gamebookComment = gamebookComment;
this.gamebookPublisher = gamebookPublisher;
this.gamebookStarRating = gamebookStarRating;
}
public long getGamebookId() {
return gamebookId;
}
public String getGamebookName() {
return gamebookName;
}
public String getGamebookComment() {
return gamebookComment;
}
public String getGamebookPublisher() {
return gamebookPublisher;
}
public float getGamebookStarRating(){
return gamebookStarRating;
}
public void setGamebookId(long gamebookId) {
this.gamebookId = gamebookId;
}
/* ADDED setters */
public void setGamebookName(String gamebookName) {
this.gamebookName = gamebookName;
}
public void setGamebookComment(String gamebookComment) {
this.gamebookComment = gamebookComment;
}
public void setGamebookPublisher(String gamebookPublisher) {
this.gamebookPublisher = gamebookPublisher;
}
public void setGamebookStarRating(float gamebookStarRating) {
this.gamebookStarRating = gamebookStarRating;
}
}
You also probably want to be able to pass the respective id to the getSingleGamebookByID, so you may wish to change this to:-
#Query("SELECT * FROM gamebooks_table WHERE gamebookId=:gamebookId /*<<<<< ADDED to use id passed */ ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID(long gamebookId /*<<<<< ADDED to use id passed */);
you would probably want to remove the comments.
Note the LiveData aspect has not been tested and is conjecture.
Example
This example shows that room is fine with your original code but that the issues is on the LiveData/Viewmodel side :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
GamebookDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Note The Database has .allowMainThreadQueries */
db = TheDatabase.getInstance(this);
dao = db.getGamebookDao();
long gb1id = dao.insert(new Gamebook("Gamebook1","blah","Gamebook1 Publisher", 10.1F));
long gb2id = dao.insert(new Gamebook("Gamebook2","blah","Gamebook2 Publisher", 6.1F));
long gb3id = dao.insert(new Gamebook("Gamebook3","blah","Gamebook3 Publisher", 10.1F));
logGameBook(dao.getSingleGamebookByID());
logGameBook(dao.getSingleGamebookByID());
logGameBook(dao.getSingleGamebookByID());
/* Alternative that allows the ID to be specified */
logGameBook(dao.getSingleGamebookByIDAlternative(gb1id));
logGameBook(dao.getSingleGamebookByIDAlternative(gb2id));
logGameBook(dao.getSingleGamebookByIDAlternative(gb3id));
}
void logGameBook(Gamebook gb) {
Log.d("GAMEBOOKINFO","Gamebook is " + gb.getGamebookName() + " id is " + gb.getGamebookId());
}
}
The above uses your original code, the TheDatabase is a basic #Database annotated class BUT with .allowMainThreadQueries so it is run on the main thread.
The log, after running, includes:-
2022-03-12 08:16:12.556 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
2022-03-12 08:16:12.558 I/chatty: uid=10132(a.a.so71429144javaroomidreturnedaszero) identical 1 line
2022-03-12 08:16:12.561 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
2022-03-12 08:16:12.568 D/GAMEBOOKINFO: Gamebook is Gamebook1 id is 1
2022-03-12 08:16:12.572 D/GAMEBOOKINFO: Gamebook is Gamebook2 id is 2
2022-03-12 08:16:12.574 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
Note how the first just returns the same object and thus id.

How do I insert values of elements that are part of the EmbeddedId in JPA?

I have a case where I need to execute an insert statement via createNativeQuery. I have an entity list I'm looping through in order to set the properties accordingly from another bean class, and then persist that data to the oracle database.
The problem I am facing is persisting the data that is part of the embeddedId (item, loc, weekstart, type, forecastId, insertTS). I need to persist that data for the new records to be inserted into the database. When I try to set the values from the POJO bean to my set method for the properties of my entity bean, nothing happens. Below is my code for setting the values of the properties from the POJO bean to my entity bean, along with my persistence method and the insert query being executed:
Validation class where validation occurs beforehand (missing to get the point) that includes the setting of my entity properties from the POJO bean:
List <InsertPromoData> insertPromos = new ArrayList<InsertPromoData>();
promo.forEach(record -> {
if (record.getErrorList().size() == 0) {
rowsSuccessful++;
Util.writeSuccessToFile(templateCd, successFile, record, successFields);
try {
InsertPromoData insertData = new InsertPromoData();
insertData.getId().setItem(record.getItem());
insertData.getId().setLoc(record.getLoc());
insertData.getId().setWeekStart(record.getWeek_Start_Date());
insertData.setNumberOfWeeks(record.getNumber_Of_Weeks());
insertData.getId().setType(record.getType());
insertData.getId().setForecastId(record.getForecast_ID());
insertData.setQty(record.getUnits());
insertPromos.add(insertData);
}
catch (Exception e) {
logger.error("Error with setting insertPromolist from promo list values and the error is " + e.getMessage());
}
}
else {
if (rowsFailure == 0) {
Util.writeHeaderToFile(templateCd, errorFile);
}
rowsFailure++;
Util.writeErrorToFile(templateCd, errorFile, record, record.getErrorList());
}
});
errorFile.close();
successFile.close();
OracleImpl.insertPromoData(insertPromos);
POJO bean (promo is the variable representing this list of beans in validation class above):
public class PromoBean extends ErrorListBean
{
public String Item;
public String Loc;
public String Week_Start_Date;
public String Units;
public String Forecast_ID;
public String Type;
public String Number_Of_Weeks;
public String getItem() {
return Item;
}
public void setItem(String item) {
Item = item;
}
public String getLoc() {
return Loc;
}
public void setLoc(String loc) {
Loc = loc;
}
public String getWeek_Start_Date() {
return Week_Start_Date;
}
public void setWeek_Start_Date(String week_Start_Date) {
Week_Start_Date = week_Start_Date;
}
public String getNumber_Of_Weeks() {
return Number_Of_Weeks;
}
public void setNumber_Of_Weeks(String number_Of_Weeks) {
Number_Of_Weeks = number_Of_Weeks;
}
public String getType() {
return Type;
}
public void setType(String type) {
Type = type;
}
public String getForecast_ID() {
return Forecast_ID;
}
public void setForecast_ID(String forecast_ID) {
Forecast_ID = forecast_ID;
}
public String getUnits() {
return Units;
}
public void setUnits(String units) {
Units = units;
}
}
Embeddable class representing the composite primary key of the table:
#Embeddable
public class PromoID implements Serializable {
#Column(name = "ITEM")
private String item;
#Column(name = "LOC")
private String loc;
#Column(name = "WK_START")
private String weekStart;
#Column(name = "TYPE")
private String type;
#Column(name = "FCSTID")
private String forecastId;
#Column(name = "U_TIMESTAMP")
private String insertTS;
public PromoID() {
}
public PromoID (String item, String loc, String weekStart, String type, String forecastId, String insertTS) {
this.item = item;
this.loc = loc;
this.weekStart = weekStart;
this.type = type;
this.forecastId = forecastId;
this.insertTS = insertTS;
}
public String getItem() {
return item;
}
public void setItem(String item) {
this.item = item;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
public String getWeekStart() {
return weekStart;
}
public void setWeekStart(String weekStart) {
this.weekStart = weekStart;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getForecastId() {
return forecastId;
}
public void setForecastId(String forecastId) {
this.forecastId = forecastId;
}
public String getInsertTS() {
return insertTS;
}
public void setInsertTS(String insertTS) {
this.insertTS = insertTS;
}
//hashcode and equals methods
Persistence Bean:
#Entity
#Table(name = "U_USER_PROMO")
public class InsertPromoData {
#EmbeddedId
private PromoID id;
#Column(name="NUMBER_OF_WEEKS")
String numberOfWeeks;
#Column(name="QTY")
String qty;
#Id
#AttributeOverrides(
{
#AttributeOverride(name = "item",column = #Column(name="ITEM")),
#AttributeOverride(name = "loc", column = #Column(name="LOC")),
#AttributeOverride(name = "weekStart", column = #Column(name="WK_START")),
#AttributeOverride(name = "type", column = #Column(name="TYPE")),
#AttributeOverride(name = "forecastId", column = #Column(name="FCSTID"))
}
)
public PromoID getId() {
return id;
}
public void setId(PromoID id) {
this.id = id;
}
public String getNumberOfWeeks() {
return numberOfWeeks;
}
public void setNumberOfWeeks(String numberOfWeeks) {
this.numberOfWeeks = numberOfWeeks;
}
public String getQty() {
return qty;
}
public void setQty(String qty) {
this.qty = qty;
}
}
DAO class method to execute the update (entitymanagerfactory emf already initialized):
public static void insertPromoData(List<InsertPromoData> insertData) {
logger.debug("Execution of method insertPromoData in Dao started");
System.out.println("Size of the insertData list is " + insertData.size());
EntityManager em = emf.createEntityManager();
try {
em.getTransaction().begin();
System.out.println("Beginning transaction for insertPromoData");
Query query = em.createNativeQuery(env.getProperty("insertPromoUploadData"));
for (InsertPromoData promoData : insertData) {
query.setParameter("item", promoData.getId().getItem());
query.setParameter("location", promoData.getId().getLoc());
query.setParameter("wkStart", promoData.getId().getWeekStart());
query.setParameter("numberOfWeeks", promoData.getNumberOfWeeks());
query.setParameter("type", promoData.getId().getType());
query.setParameter("fcstId", promoData.getId().getForecastId());
query.setParameter("quantity", promoData.getQty());
query.executeUpdate();
}
em.getTransaction().commit();
}
catch(Exception e) {
logger.error("Exception in beginning transaction");
e.printStackTrace();
}
finally {
em.clear();
em.close();
}
logger.debug("Execution of method insertPromoData in Dao ended");
}
Query in properties file:
insertPromoUploadData = INSERT INTO {h-schema}U_USER_PROMO (ITEM, LOC, WK_START, NUMBER_OF_WEEKS, TYPE, FCSTID, QTY, U_TIMESTAMP) VALUES (:item, :location, TO_DATE(:wkStart,'MM DD YYYY'), :numberOfWeeks, :type, :fcstId, :quantity, SYSDATE)
My list size from my DAO class is returning as 0 once I begin the transaction and not sure why it is empty. Is there a reason that it is empty? I'm trying to persist each of the fields to the database (including the composite key fields) via insert query. Any help appreciated.
After looking into this for hours, I finally came to the conclusion that the simplest way to executeUpdate() without running into issues due to my current #EmbeddedId/#Embeddable logic was to change it to use #IdClass for my composite PK class, and annotate the fields from the PK in my entity with #Id. This allowed my data to be persisted to the database. Another slight difference was adding the insertTS in my entity class and annotating with #Id and generating getters/setters. This was necessary for JPA to recognize all the properties being referenced that I am wanting to persist, though I am persisting insertTS using SYSDATE function from the oracle DB instead of utilizing the get/set methods and setting to the current time from the java side.
I am sure there is a way to use #EmbeddedId/#Embeddable logic and be able to persist the fields that are part of the EmbeddedId, however, this I found to be a more simplistic way of doing it without further complexity in the code.

MongoDB Aggregation of Parent document field in Spring

I am unable to aggregate the field from parent document,
Example:
Class Animal
{
int legs;
int eyes;
HashMap<String, Object> subSpecies;
}
Class Tiger extends Animal{
int id;
String name;
String Category;
}
public static void main(String args[]) {
UnwindOperation unwind = unwind("$subSpecies");
Aggregation agg = Aggregation.newAggregation(
Aggregation.match(Criteria.where("category").is("Tigers")),
unwind,
Aggregation.group("subSpecies.BengalTiger").count().as("count"),
project("count","subSpecies.BengalTiger").and("subSpecies.BengalTiger").previousOperation(),
Aggregation.sort(Sort.Direction.DESC, "count")
);
MyServices services = new MyServices();
//Convert the aggregation result into a List
List<TigerGroupCount> result =
services.getAggregationList(agg, Tiger.class, TigerGroupCount.class);
System.out.println("result:" + result + ",,,,,,,,,," + result.size());
for (TigerGroupCount tigerGroupCount: result) {
System.out.println("Status : " + tigerGroupCount.groupName);
System.out.println("Count : " + tigerGroupCount.count);
}
mLogger.info(""+result.size());
}
class TigerGroupCount
{
int id;
String groupName;
long count;
}
But here, I am able aggregate with Tiger.name field which is a direct field in Tiger.class. Need to access field - subSpecies which is in parent class(Animal.class).
Any small help is Appreciable.
Thank you in advance.

How to select several properties for specific name

I am working on a web project using Spring and Spring MVC.
I have a feature that is the same for 3 different elements (which are available in dropdown in view). Only two parameters change for each item. I decided to put these elements and parameters in a .properties file to permit the user change them. So for example in my .properties I have the following:
FC
fcUuid=11111111111111111
fcTag=tag1
AC
itUuid=22222222222222222
itTag=tag2
IT
acUuid=333333333333333333
acTag=tag3
For the moment I am able to retrieve each element separately.
For example:
String communityUuid = SpringPropertiesUtil.getProperty("fcUuid");
(SpringPropertiesUtil extends PropertyPlaceholderConfigurer)
But my question is: how can I retrieve all the parameters relative to one element?
For example the user selects "FC", how in my service layer can I retrieve both fcUuid and fcTag parameters?
Of course I can do something like:
if(param="FC"){
String communityUuid = SpringPropertiesUtil.getProperty("fcUuid");
String communityTag = SpringPropertiesUtil.getProperty("fcTag");
} else if (param="AC"){...}
But I don't want to do that because the user can add elements so I would have to modify the code each time.
I would like something like:
String communityUuid = SpringPropertiesUtil.getProperties(param[0]);
String tagUuid = SpringPropertiesUtil.getProperties(param[1]);
Or even better:
String communityUuid = SpringPropertiesUtil.getProperties(param[uuid]);
String tagUuid = SpringPropertiesUtil.getProperties(param[tag]);
You need customize how to handle properties into map that you need. You can do like :
#group your properites
uiValues=\
FC={fcUuid:11111111111111111},{fcTag : tag1}&&\
AC={itUuid : 22222222222222222},{itTag : tag2}&&\
IT={acUuid:333333333333333333},{acTag:tag3}
#Component
public class ConfigProperties {
//FC=...&&AC=....&&IT=....
private static final String GROUP_SPLITTER = "&&";
private static final String GROUP_VALUES_MARKER = "=";
private static final String START_VALUES_IN_GROUP = "{";
private static final String END_VALUES_IN_GROUP = "}";
private static final String VALUES_SPLITTER= ",";
private static final String KEY_VALUE_SPLITTER= ":";
#Value("#{T(current current package .ConfigProperties).
decodeMap('${uiValues}')}")
private Map<String,Values> map;
/**
if(param="FC"){
String communityUuid = SpringPropertiesUtil.getProperty("fcUuid");
String communityTag = SpringPropertiesUtil.getProperty("fcTag");
}
#Autowired
ConfigProperties configProperties;
String communityUuid = configProperties.getValue("FC","fcUuid");
String communityTag = configProperties.getValue("FC","fcTag");
*/
public String getValue(String key , String property){
//add check for null
Values values= map.get(key);
if (values == null){
return "";
}
for (Tuple tuple : values.tuples){
if (tuple.key.equals(property)){
return tuple.value;
}
}
return "";
}
public List<String> getProperties(String key){
//add check for null
List<String> properties = new ArrayList<>();
Values values= map.get(key);
//add check for null
for (Tuple tuple : values.tuples){
properties.add(tuple.key);
}
return properties;
}
public static Map<String, Values> decodeMap(String value) {
//add validator for value format
boolean isValid = true;
if(!isValid){
return new HashMap<>();
}
Map<String, Values> map = new LinkedHashMap<>();
String[] groups = value.split(GROUP_SPLITTER);
for (String group : groups) {
String[] values = splitToKeyAndValues(group.split(GROUP_VALUES_MARKER)[1]);
String key = group.substring(0,group.indexOf(GROUP_VALUES_MARKER));
map.put(key, getValues(values));
}
return map;
}
private static Values getValues(String[] parts) {
Values values = new Values();
for (int i=0;i<parts.length;i++){
values.tuples.add(getTuple(parts[i]));
}
return values;
}
private static Tuple getTuple(String parts) {
Tuple tuple = new Tuple();
parts = parts.substring(1,parts.length()-1);
tuple.key= parts.split(KEY_VALUE_SPLITTER)[0];
tuple.value= parts.split(KEY_VALUE_SPLITTER)[1];
return tuple;
}
static String[] splitToKeyAndValues(String valuesInGroup) {
return valuesInGroup.split(VALUES_SPLITTER);
}
}
class Values{
List<Tuple> tuples = new ArrayList<>();
}
class Tuple{
String key;
String value;
}
With the help of one of my colleagues I managed to realize that. This is how I proceeded:
In my .properties file I changed the data format, now it looks like:
#FC
clientApplications[0].name=FC
clientApplications[0].communityId=00000000000000
clientApplications[0].tag=tag0
#AC
clientApplications[1].name=AC
clientApplications[1].communityId=11111111111111
clientApplications[1].tag=tag1
etc...
I created a bean named ClientApplication (FC, AC and IT are applications) with 3 attributes (name, communityId and tag)
I created a class named ApplicationStore that stores all the applications present in the propertiesfile in the form of ClientApplication objects and that provides a get method which returns a ClientApplication according to the name of the app.
#Component("applicationStore")
public class ApplicationStore {
private Map<String, ClientApplication> map;
public void put(String key, ClientApplication value) {
map.put(key, value);
}
public ClientApplication get(String key) {
return map.get(key);
}
public ApplicationStore() {
int i = 0;
map = new HashMap<String, ClientApplication>();
while (SpringPropertiesUtil.getProperty("clientApplications[" + i + "].name") != null) {
ClientApplication ca = new ClientApplication();
ca.setName(SpringPropertiesUtil.getProperty("clientApplications[" + i + "].name"));
ca.setCommunityId(SpringPropertiesUtil.getProperty("clientApplications[" + i + "].communityId"));
ca.setTag(SpringPropertiesUtil.getProperty("clientApplications[" + i + "].tag"));
map.put(ca.getName(), ca);
i++;
}
}
}
With that I only have to add this to my service layer:
#Service("aService")
public class AServiceImpl implements AService {
#Autowired
private ApplicationStore apps;
private String communityUuid;
private String communityTag;
#Override
public void aMethod(String appName) trhows Exception {
ClientApplication ca = new ClientApplication();
ca = apps.get(appName);
communityUuid = ca.getCommunityId();
communityTag = ca.getTag();
System.out.println("Application for key " + app + " : " + ca);
System.out.println("communityUuid: " + communityUuid);
System.out.println("communityTag:" + communityTag);
}
}

Spring data elasticSearch returns null with findOne

I'm testing Spring data with elasticSearch. The ES server is running on a remote server in tha same room.
I have one index created a day, under an alias. I'm trying to find a simple tweet. But when I try a findOne(), it doesn't seem to work because it returns always null.
Also, findAll(ids) doesn't work because I'm using the alias, but I can't find in the documentation how to handle this.
What do I want to achieve ?
For the moment, simply retrieve a tweet with a given id_str.
The count method works, the findOne doesn't
Here are my questions
What should I do to make findOne() to work ?
Which way should I use to search on multiple indexes in this alias ?
Here is how the datas looks like in ES
{
"id_str" : "135131315100051",
"..." : "...",
"user" : {
"id_str" : "15843643228"
"..." : "..."
}
}
My model
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
#Document(indexName = "alias", type = "tweets")
public class Tweet
{
#Id
#Field(type = FieldType.String)
private String idStr;
public String getIdStr()
{
return idStr;
}
public void setIdStr(final String idStr)
{
this.idStr = idStr;
}
#Override
public String toString()
{
return "{ id_str : " + idStr + " }";
}
}
Alias is alias, and indexes are alias_dd-mm-yyyy
My repository
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.thales.communications.osintlab.bigdata.webservices.models.Tweet;
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
Tweet findByIdStr(String idStr);
}
My test
#Test
public void shouldReturnATweet()
{
//lets try to search same record in elasticsearch
final Tweet tweet1 = tweetRepository.findOne("593768150975512576");
//final Tweet tweet = tweetRepository.findByIdStr("593897683661824000");
System.out.println("Count is " + tweet1);
//System.out.println("Count is " + tweetRepository.count());
// System.out.println(tweet.toString());
}
Of course, the tweet with the tested Id exists :). And the count() is working fine.
Thanks for your help
EDIT
Here is a sample application of what I have : https://github.com/ogdabou/es-stackoverflow-sample
It seems that spring-data-elasticsearch is look for the field "_id" and not the field "id_str". Maybe because of method parsing (look there). I'm looking for a way to bind my json "id_str" attribute to my idStr java model.
What was the real issue
We set the _id field of our tweet in Elasticsearch with the id field given by twitter. But it saves it in another format ( eg 132 becomes 1.32E2)
When I'm going a findOne() it is searching for a match with the Elasticsearch _id field and not the id_str I needed.
Solution
There, you have 2 commits, the first is the issue, the second the solution.
New repository
public interface EsTweetRepository extends ElasticsearchRepository<Tweet, String>
{
#Query("{\"bool\" : {\"must\" : {\"term\" : {\"id_str\" : \"?0\"}}}}")
Tweet findByIdStr(String idStr);
}
The model
#Document(indexName = "my_index_01", type = "tweets")
public class Tweet
{
// Elasticsearch object internal id. Look at field "_id"
#Id
private String id;
// Twitter internal id, saved under the "id_str" field
#Field(type = FieldType.String)
private String id_str;
#Field(type = FieldType.String)
private String text;
public String getId_str()
{
return id_str;
}
public void setId_str(final String id_str)
{
this.id_str = id_str;
}
public String getText()
{
return text;
}
public void setText(final String text)
{
this.text = text;
}
public String getId()
{
return id;
}
public void setId(final String id)
{
this.id = id;
}
#Override
public String toString()
{
return "{ _id : " + id + ", id_str : " + id_str + ", text : " + text + " }";
}
}

Resources