What i want
I want to store a pojo with a list of objects as a json object into a collection. I do not know what objects are getting inserted into my pojo, thats why i need to keep it as generic as possible.
My approach
Im using the reactive stream framework from mongo db.
The Pojo that should contains a list of objects
public class Entity {
public ObjectId id;
public String type;
public int version;
public Set<Object> components;
public Entity() { }
public Entity(ObjectId id, String type, int version, Set<Object> components) {
this.id = id;
this.type = type;
this.version = version;
this.components = components;
}
}
The pojo that i created as some sort of test object.
public class Chunk {
public ObjectId id;
public int x;
public int y;
public Set<ObjectId> inChunk;
public Chunk() { }
public Chunk(int x, int y, Set<ObjectId> entities) {
this.x = x;
this.y = y;
this.inChunk= entities;
}
}
My main
public static void main(String[] args) throws Throwable {
var pojoCodecRegistry = fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().register(Object.class).automatic(true).build())
);
MongoClientSettings settings = MongoClientSettings.builder()
.applyConnectionString(connString)
.codecRegistry(pojoCodecRegistry)
.retryWrites(true)
.build();
// Receiving database and collection
var mongoClient = MongoClients.create(settings);
var database = mongoClient.getDatabase("Test");
var collection = database.getCollection("Entity", Entity.class);
// Create an entity to fill it with components and insert it
var entity = new Entity(new ObjectId(), "player", 1, new LinkedHashSet<>());
var chunk = new Chunk(1,2, new LinkedHashSet<>());
entity.components.add(chunk);
// Insert it
var operationSubscriber = new SubscriberHelpers.OperationSubscriber<>();
collection.insertOne(entity).subscribe(operationSubscriber);
operationSubscriber.await();
// Receive it
var printDocument = new SubscriberHelpers.PrintSubscriber<>("YO : ");
collection.find().subscribe(printDocument);
printDocument.await();
mongoClient.close();
}
The problem
The saved object is getting inserted, but its missing its object id. Furthermore, after loading, the entity.components is filled with one single object. But this object is just a raw "Object.class" from java and i cant cast that one to my saved "Chunk.class". So it somehow forgot its type upon loading and i cant acess it.
My question
How do i save a list of "unknown" objects in my Entity.class and make sure that its getting loaded correctly with the type it was previously ?
Furthermore i would like to know if its then possible for subdocuments to reference other "Entity.class" aswell. Like having a "Chunk.class" inside Entity.components that references a few other "Entity.class" objects ( Note, not subdocuments ). And if thats the case, would they automaticly load ? Or do i need to implement that by myself ?
Im glad for any help, thanks !
Related
I'm trying to figure out how can i store Long info about a Discord user in an array.
Tommy on discord type: ;create
then
Mike on discord type : ;create
public class Create extends ListenerAdapter {
public void onMessageReceived(MessageReceivedEvent event) {
if (!event.isFromGuild()) return;
String[] messageSent = event.getMessage().getContentRaw().split(" "); //for further code
String name = event.getMember().getUser().getName();
long idLong = event.getMember().getUser().getIdLong();
String idString = event.getMember().getUser().getId(); //not for Stackoverflow question
long base = 1L; //everyone start with 1 (L is because we are using Long version of int value)
if (messageSent[0].equalsIgnoreCase(";Create")) {
ArrayList<Long> dcbase = new ArrayList<>(); //Long is used to store getIdLong value
dcbase.add(idLong); //
dcbase.add(base); // 1L is default value
event.getChannel().sendMessage("Here is the array "+ dcbase).queue();}}
Now the problem is if i want my ArrayList to be for many user I would need an ArrayList of ArrayList. Arraylist<ArrayList<Long>>
But to search through them I would like to do search using the IdLong value.
I tried to replace dcbase as idLong but its already defined.
Is there any way i can do that?
Because what i want to do next is have a method that goes to the idLong of Tommy and pull out the [1] of the Tommy ArrayList.
I plan to store the info to a file that way and will have longer Arrays:
177877878787 1 0 0 //Tommy IdLong, base Long, stuff i'll add, stuff i'll add
121244777778 1 //Mike IdLong, base Long
//New line for new member.
Since I don't know on which line the required IdLong will be stored in the file, i need a reference to search it.
I am self-thaught, I hope I am clear enough.
You want a Map. The key is the user id (as a long) and the value is your number:
private final Map<Long, Long> map = new HashMap<>();
#Override
public void onMessageReceived(MessageReceivedEvent event) {
...
map.put(id, base);
...
}
You can then simply access the value by using map.get(id). Instead of using lists, I would recommend using proper objects with defined fields for whatever data you want to store.
For the case of persistence, instead of just writing your data to some file, use a relational database like PostgreSQL or SQLite. This way you can easily update them at runtime without having to read/write the entire map every time you want to access it.
A very helpful thing to use for this is the Java Persistence API (JPA).
#Entity
#Table
public class UserInfo {
#Id
private long id;
private long base;
public UserInfo(long id, long base) {
this.id = id;
this.base = base;
}
public void setId(long id) {
this.id = id;
}
public long getId() { return this.id; }
public void setBase(long base) {
this.base = base;
}
public long getBase() { return this.base; }
}
I have 3 POJO classes. Recipe, Ingredient and Step.
I want to be able to browse my recipes when offline, so I decided to use Room.
Recipe.class
#Entity(tableName = "recipe")
public class Recipe implements Parcelable {
#PrimaryKey(autoGenerate = false)
#SerializedName("id")
public int recipeId;
#ColumnInfo(name = "recipe_name")
public String name;
#TypeConverters(Converters.class)
public List<Ingredient> ingredients = null;
#TypeConverters(Converters.class)
public List<Step> steps = null;
#ColumnInfo(name = "recipe_servings")
public int servings;
#Ignore
public String image;
public Recipe(int recipeId, String name, List<Ingredient> ingredients, List<Step> steps, int servings, String image) {
this.recipeId = recipeId;
this.name = name;
this.ingredients = ingredients;
this.steps = steps;
this.servings = servings;
this.image = image;
}
...
//getters and setters
...
Converters.class
public class Converters {
static Gson gson = new Gson();
#TypeConverter
public static List<Ingredient> stringToIngredientList(String data) {
if (data == null) {
return Collections.emptyList();
}
Type listType = new TypeToken<List<Ingredient>>() {}.getType();
return gson.fromJson(data, listType);
}
#TypeConverter
public static String ingredientListToString(List<Ingredient> ingredients) {
return gson.toJson(ingredients);
}
#TypeConverter
public static List<Step> stringToStepList(String data) {
if (data == null) {
return Collections.emptyList();
}
Type listType = new TypeToken<List<Step>>() {}.getType();
return gson.fromJson(data, listType);
}
#TypeConverter
public static String stepListToString(List<Step> steps) {
return gson.toJson(steps);
}
}
RecipeDatabase.class
#Database(entities = {Recipe.class}, version = 1)
abstract class RecipeDatabase extends RoomDatabase {
private static RecipeDatabase INSTANCE;
public abstract RecipeDao recipeDao();
public static RecipeDatabase getRecipeDatabase(Context context) {
if (INSTANCE == null) {
INSTANCE =
Room.databaseBuilder(context.getApplicationContext(), RecipeDatabase.class, "recipe-database")
// allow queries on the main thread.
// Don't do this on a real app! See PersistenceBasicSample for an example.
.allowMainThreadQueries()
.build();
}
return INSTANCE;
}
public static void destroyInstance() {
INSTANCE = null;
}
}
RecipeDao.class
#Dao
public interface RecipeDao {
#Query("SELECT * FROM recipe")
List<Recipe> getAll();
#Query("SELECT * FROM recipe where recipe_name LIKE :name")
Recipe findByName(String name);
#Query("SELECT COUNT(*) from recipe")
int countRecipes();
#Update
void update(Recipe... recipes);
#Insert
void insertAll(Recipe... recipes);
#Delete
void delete(Recipe recipe);
}
My question: After saving the List<Step> and List<Ingredient> as Strings using the Converters class, should I also save a database of each of my Step.class and Ingredient.class? Should I include the #Entity annotation for these classes too? Should I make a StepDatabase and an IngredientDatabase? Is that also needed to be able to access my Recipes when offline?
should I also save a database of each of my Step.class and Ingredient.class. Should I make a StepDatabase and an IngredientDatabase?
By all means no. You do not create database per entity, but instead you create a table per entity. In most apps one database is enough.
Traditionally, SQL databases (relational database by nature) should enforce normalization rules, especially in enterprise systems on servers. Shortly, normalization rules refer to series of refactoring to a relational database design in a manner where each entity is extracted to its own table with foreign keys in the parent table. You'd then have to use SQL SELECT with JOINs to get the data together.
With that said, it's quite common to break the normalization rules for mobile apps and keep the data model as simple as possible by storing the nested entities, in your case, ingredients, and store them as JSON strings or whatever makes sense and easy for you to handle in your code.
So to summarize, what you currently have appear to be really good. As long as it simplifies your code - go for it. As far as I know, Room does not support JOINs, but there's something pretty close.
There's a good article on Medium that discusses advanced subjects with Room.
Yes, In my opinion it's a good practice to declare a model for each table first and Yes, Every model you should include #Entity annotation telling hibernate that what you are mapping is a table from your db.
I'm using ORMLite, trying to use the ForeignCollectionKey but I got the following error :
Internal DAO object is null. LazyCollections cannot be used if they have been deserialized.
I've my object named Zone :
public class Zone implements Serializable {
private static final long serialVersionUID = 1L;
public static final String ZONE_ID = "id";
public static final String ZONE_PARENT_ID = "parentZoneId";
#DatabaseField(generatedId=true)
private int id;
#DatabaseField()
String name;
#DatabaseField(foreign=true, foreignAutoRefresh = true)
Zone parentZone;
#ForeignCollectionField(foreignFieldName = "parentZone", eager = true)
private ForeignCollection<Zone> zoneChild;
public Zone() {
// TODO Auto-generated constructor stub
}
public ForeignCollection<Zone> getZoneChild() {
return zoneChild;
}
public void setZoneChild(ForeignCollection<Zone> zoneChild) {
this.zoneChild = zoneChild;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
In a class i'm doing a recursive method to get all my zone child objects :
public void getZone(Zone zone, Dao<Zone, Integer> tempZoneDao){
ZoneListEntity zoneEntity = new ZoneListEntity();
zoneEntity.setName(zone.getName());
zoneEntity.setNiveau(0);
zoneEntity.setZone(zone);
mainZoneList.add(zoneEntity);
List<Zone> childList = new ArrayList<Zone>(zone.getZoneChild());
//set rootZone's children as ZoneListEntity
for(Zone currentZone : childList){
ZoneListEntity zoneGroup = new ZoneListEntity();
zoneGroup.setName(currentZone.getName());
zoneGroup.setZone(currentZone);
System.out.println("Zone : "+currentZone.getName());
getZone(currentZone, tempZoneDao);
}
}
When i'm entering for the first time in my getZone, everything going well. Then when I loop in getZone the application crashes trying to access to the child zone :
List<Zone> childList = new ArrayList<Zone>(zone.getZoneChild());
Do you have any ideas ? Is my model construction right ?
Thanks
Do you have any ideas ? Is my model construction right ? Thanks
So the exception message is trying to explain what is going on. I'm not sure how it can be improved.
Internal DAO object is null. LazyCollections cannot be used if they have been deserialized.
You are trying to access zoneChild which is a ForeignCollection that has been deserialized. Since it has been deserialized all of the underlying database configurations and connections could not be reestablished. I guess this can happen when it stored in an Android Bundle? I'm not sure if this is the only case.
If you need to get the Zone children you are going to have to either call dao.refresh() on the entity after you deserialize it or do the query yourself by doing the zoneDao.
I solved this problem like Gray suggested: pass the primary key attribute in the Bundle and then obtain the object again from the database in the destination Activity:
Example:
Let's suppose I want to pass a Person object and that I've declared Person.name as:
#DatabaseField (columnName ="name")
private String name;
Then:
ActivityA
Intent intent = new Intent(ActivityA.this, ActivityB.class);
Bundle bundle = new Bundle();
bundle.putString("NAME" Person.getName());
intent.putExtras(bundle);
ActivityB
String name = getIntent().getExtras().getString("NAME"));
Person p = getHelper().getPersonDao().queryForEq("name", name);
And there you are, your Collection will be refreshed.
Im a beginner in Java. I read that structs in C are similar to classes in Java, but I have the following doubt.
I have a class as follows:
public class operations {
public Integer[] stream;
public Integer[] functi;
public String[] name;
public Integer[] funcgroup;
}
I get an input from the user for name and compare it with the name array in the class and if there is a match, I want to return the records for all the other fields corresponding to the name.
For eg. if name corresponds to String[5], then I want to output all the records corresponding to [5]..i.e stream[5], functi[5], functigroup[5].
How can I do this?
EDIT Now my program looks like this:
public class operations extends DefFunctionHandler {
public ArrayList<Integer> stre = null;
public ArrayList<Integer> functii = null;
public ArrayList<String> nmee = null;
public ArrayList<Integer> funcigroup = null;
public ArrayList<Integer> sourcee = null;
public void filter(String x){
DefFunctionHandler defi = new DefFunctionHandler();
functii = defi.getFunc();
stre = defi.getStream();
nmee = defi.getName();
funcigroup = defi.getFuncgroup();
sourcee = defi.getSource();
Map<String, operations> map = new HashMap<String, operations>();
operations operations = new operations(0, 0, x, 0, 0);
map.put(x, operations);
operations op = map.get("flush");
System.out.println(op.toString());
}
And I get a message saying that I have to declare a constructor for operations with parameters(int, int, string,int, int). Can anyone tell me if my Map interface implementation is correct?
You should store your operation objects into a Map
A map works with key/value, you put an id into the map, and you can retrieve the corresponding key.
In your example, use the class Operation :
public class Operation {
public int stream;
public int functi;
public name;
public int funcgroup;
}
and a map like this :
Map<String, Operation> map = new HashMap<String, Operation>();
Operation operation = new Operation(0,0,"name5", 0);
map.put("name5", operation);
You can retrieve your Operation object with :
Operation op = map.get("name5");
In my function, in order to accept an array of values I have to define a data type...
myFunc( String[] myVar ) {}
What if I wanted an array like the below array that mixes objects and strings and nested arrays/ (Note: the below array is in PHP)
myFunc(array(
array(
'name' => 'Category1',
'products' => array(
array(
productObject,
productObject,
...
)
)
)
));
Is this possible in Java or is this a completely wrong technique?
You would need to use an object-oriented approach in Java (which will also work in PHP). The class structure for the object you've listed above would be as follows:
public class MyObject {
String name;
Product[] products;
public MyObject(String name, Product[] products) {
this.name = name;
this.products = products;
}
}
Then you can get an instance of that class by doing the following:
new MyObject("Category1", new Product[] { productObject1, productObject2 });
You can also have an array of this object type:
MyObject[] myObjs = {
new MyObject("Category1", new Product[] { productObject1, productObject2 });
new MyObject("Category2", new Product[] { productObject3, productObject4 });
new MyObject("Category3", new Product[] { productObject5, productObject6 });
}
It's possible (an array of Object), but it's still not a good solution.
It sounds like instead of an array that holds different types, you really want to create a new class. You have names defined for the array elements in your example, so that indicates that you know what types they should be. My guess is that you want to create a class that holds a String for the name and an array of Products (another custom class).
Most PHP code is not as strongly typed as code in a more "traditional" OO language like C++, C#, Java, etc. So an approach of porting PHP directly to Java is probably off on the wrong track.
Instead, try building a class to represent your data type. Start from the innermost type, which in your case seems to be a "Product". Example:
public class Product {
private int id;
private String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
}
Now you need to represent what appears to be a category of products, so you can create another class to hold those. Example:
public class ProductCategory {
private String name;
private List<Product> products;
public ProductCategory(String name) {
this.name = name;
this.products = new ArrayList<Product>();
}
public String getName() {
return this.name;
}
public List<Product> getProducts() {
return this.products;
}
}
To use these classes, you might write code like this:
Product p1 = new Product(1, "P1");
Product p2 = new Product(2, "P2");
Product p3 = new Product(3, "P3");
ProductCategory c1 = new ProductCategory("C1");
// Add product to category 1
c1.getProducts().add(p1);
ProductCategory c2 = new ProductCategory("C2");
// Add products to category 2
c2.getProducts().add(p2);
c2.getProducts().add(p3);
You can create an Object array, which would happily take multiple data types. You'll have to be careful with how you cast them to use them though - using instanceof is handy, or if you know exactly what data type to expect you can cast without the need to check the data type first. It's considered bad practice to use multiple data type arrays, although there's nothing stopping you from doing it if your implementation requires it.
It's possible with an array of Object, which is the base type in Java, but for your use case a custom class is more javaish.
class Category {
String name;
List<Product> products;
}
With that approach you have a type save way to define your data.