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.
Related
Q: I have a Bank class containing multiple loan accounts (LoanAccount class). I've create a LoanAccountService that have the CRUD functionalities. My concerns are about how I implemented the update functionality.
Bank
public class Bank {
private List<LoanAccount> loanAccounts;
}
Loan account
public class LoanAccount {
private String id;
private Integer numberOfInstallments;
private LoanAccountType type;
private Date creationDate;
private BigDecimal loanAmount;
}
Service
public class LoanAccountService{
private Bank bank;
public LoanAccountService(Bank bank) {
this.bank = bank;
}
public LoanAccount update(LoanAccount loanAccount) {
Optional<LoanAccount> account = bank.getLoanAccounts()
.stream()
.filter(la -> la.getId().equals(loanAccount.getId()))
.findAny();
if (account.isPresent()) {
account.get().setCreationDate(loanAccount.getCreationDate());
account.get().setLoanAmount(loanAccount.getLoanAmount());
account.get().setNumberOfInstallments(loanAccount.getNumberOfInstallments());
account.get().setType(loanAccount.getType());
} else {
throw new IllegalArgumentException("The object does not exist.");
}
return loanAccount;
}
}
When the method update is called with a LoanAccount containing an id that already exists in loanAccounts list, I want to update the existing object with the object loanAccount given as parameter.
Above is my implementation, but I feel like there should be better ways to do it.
Use Builder for getter and setter
public class LoanAccount {
private String id;
private Integer numberOfInstallments;
// add other properties
public String getId() {
return id;
}
public LoanAccount setId(String id) {
this.id = id;
return this;
}
public Integer getNumberOfInstallments() {
return numberOfInstallments;
}
public LoanAccount setNumberOfInstallments(Integer numberOfInstallments) {
this.numberOfInstallments = numberOfInstallments;
return this;
}
Use this one for update method
public LoanAccount update(LoanAccount loanAccount) {
return bank.getLoanAccounts()
.stream()
.filter(la -> la.getId().equals(loanAccount.getId()))
.findFirst().orElseThrow(IllegalArgumentException::new)
.setCreationDate(loanAccount.getCreationDate())
.setLoanAmount(loanAccount.getLoanAmount())
.setNumberOfInstallments(loanAccount.getNumberOfInstallments())
.setType(loanAccount.getType());
}
You could use a HashMap where the TKey is the type of your LoanAccount.id.
Then call loanAccounts.put(id, object)
This will update the object if there is already an Id and add a new object if not.
This is a cheap, dirty way. Another way of doing it would be to make your LoanAccount class implement Comparable and in the compareTo() method make a id based comparation.
Do the same thing overriding your equals() and you should be ready to go.
#Override
public boolean equals(object obj) {
if (obj == null) return false;
return ((LoanAccount)obj).getId() == this.getId();
}
something like that.
(code wrote by memory, can have errors and lacks validations like the data type)
What kind of persistence layer do you use?
why do you need to loop through all of the bank accounts?
Did you fetch all the accounts from the repository and loop over the service layer? If so why?
why not you fetch the corresponding single record from repository and update?
Why not you use to find and update the records instead of using the above points?
These questions may give you an idea. If you answering it !!!
If not let we discuss deeper
I have enum class that describes possible tickets types and have custom atribute to keep ticketId. When I try to add some tickets to ArrayList it makes all tickets of type X have the same ticketId. Why is that and what's more important how can I solve it?
Simplified enum class:
public enum Ticket {
FirstClass(0),
PremiumClass(1),
EconomyClass(2);
private int elementId;
private Long ticketId;
Ticket(int elementId) {
this.elementId=elementId;
}
public Long getTicketId() {
return ticketId;
}
public void setTicketId(Long ticketId) {
this.ticketId = ticketId;
}
}
Simplified method:
public void myMethod() {
ArrayList<Ticket> tickets = new ArrayList<>();
Ticket ticket = Ticket.FirstClass;
ticket.setTicketId(1L);
tickets.add(ticket);
ticket = Ticket.FirstClass;
ticket.setTicketId(2L);
tickets.add(ticket);
}
It is happening because there is only one instance for every enum constant. Calling Ticket.FirstClass will fetch the same instance every time. So you are adding the same object to the list twice.
There is really only one instance of Ticket.FirstClass, and there will only ever be one instance.
What you really have isn't a Ticket, but a TicketType. You should have a separate class for Ticket.
FirstClass is an instance of Ticket so when you're calling it, it's always the same one, you take the reference to the same object.
What you would need is a model with a Ticket and a TicketType
enum TicketType {
FirstClass,
PremiumClass,
EconomyClass
}
class Ticket {
private TicketType type;
private Long ticketId;
Ticket(TicketType type, long ticketId) {
this.type = type;
this.ticketId = ticketId;
}
}
// ---------------------------------------------------------
// And
public void myMethod() {
ArrayList<Ticket> tickets = new ArrayList<>();
Ticket ticket = new Ticket(TicketType.FirstClass, 1L);
tickets.add(ticket);
ticket = new Ticket(TicketType.FirstClass, 2L);
tickets.add(ticket);
}
Don't use enums for dynamic values,see sonar's "enum" fields should not be publicly mutable
enums are generally thought of as constant, but an enum with a public field or public setter is not only non-constant, but also vulnerable to malicious code.
See example of making enum dynamic by making it implement an interface
Nothing different from other answers, just to add some colors.
When you obtain firstClass you actually are getting same object, and adding that again.
Each enum value (you have got three) exists only once and each time you are using one of them you are just reusing one of those three values by creating a reference, there are no copies.
This means if you change the ticketId on the FirstClass value, it is changed wherever that value is referenced.
It seems you want to model a little ticket system. Each object having an own identity should be modeled as a class which has a property for the type and the id:
public class Ticket {
private Long ticketId;
private TicketType type;
public Long getTicketId() {
return ticketId;
}
public void setTicketId(Long ticketId) {
this.ticketId = ticketId;
}
public TicketType getType() {
return type;
}
public void setType(TicketType type) {
this.type = type;
}
}
public enum TicketType {
FirstClass(0),
PremiumClass(1),
EconomyClass(2);
private final int elementId;
Ticket(int elementId) {
this.elementId = elementId;
}
public int getElementId() {
return elementId;
}
}
Then you can use it this way:
public void myMethod() {
ArrayList<Ticket> tickets = new ArrayList<>();
Ticket ticket = new Ticket();
ticket.setType(TicketType.FirstClass);
ticket.setTicketId(1L);
tickets.add(ticket);
ticket = new Ticket();
ticket.setType(TicketType.FirstClass);
ticket.setTicketId(2L);
tickets.add(ticket);
}
I don't know why you need an elementId on the ticket type, that's why I just left it there (without using it). Probably you should rename ticketId to just id to keep it simple.
If the ticket's type or ticketId are never changed after assigning them you may want to remove the setters and assign the values in the constructor of Ticket (and make the attributes final).
Even if it's ok that they are changeable, you may introduce such a constructor to have code which is better readable:
In Ticket.java:
public Ticket(Long ticketId, TicketType type) {
this.ticketId = ticketId;
this.type = type;
}
Then you can write:
tickets.add(new Ticket(1L, TicketType.FirstClass));
If ticket is a persisted entity (which gets inspected by a framework like Hibernate) you might have to keep a no-args constructor to make it instantiable when loading it from a database.
I have a Book class:
public class Book extends SugarRecord {
private String mBookName;
private String mAuthorName;
private List<Page> mPageList;
public Book() {
}
public Book(String bookname, String authorName) {
mBookName = bookname;
mAuthorName = authorName;
mPageList = new ArrayList<>();
}
public String getAuthorName() {
return mAuthorName;
}
public void setAuthorName(String authorName) {
mAuthorName = authorName;
}
public String getBookName() {
return mBookName;
}
public void setBookName(String bookName) {
mBookName = bookName;
}
public void addPage(Page page) {
mPageList.add(page);
}
}
and the Page class:
public class Page extends SugarRecord {
private String mText;
public Page() {
}
public Page(String text) {
mText = text;
}
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
}
I am testing it with this:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Book book = new Book("Some Book Title", "John Doe");
Page page1 = new Page("Once upon a time there was a very lonely bunny who wanted some friends.");
Page page2 = new Page("So he found some friends, and everyone was happy.");
Page page3 = new Page("The end!");
book.addPage(page1);
book.addPage(page2);
book.addPage(page3);
book.save();
}
}
However it is not working as expected. It is trying to make mPageList its own column with this .schema:
CREATE TABLE BOOK ( ID INTEGER PRIMARY KEY AUTOINCREMENT , M_AUTHOR_NAME TEXT, M_BOOK_NAME TEXT, M_PAGE_LIST );
What I'd really like it to do is not treat the list as its own column but instead save the Pages to the PAGE table, with additional ids that reference this Book class (so what I am expecting is something like ID, BOOK_ID, M_TEXT). In short, persistence operations that cascade through nested child objects.
Can this be done in SugarORM?
No ORM database(SugarORm, DBFLow etc) supports List column. As you know sql don't have this datatype as well.
That's the reason why you are getting this error. If you ask me how you are saving list to ORM. I use Gson.
Declare Pagelist as string.
String Pagelist;
Before saving it to database convert it to Json string with the help Gson library.
Gson gson = new Gson();
String value = gson.toJson(your_page_list);
when retrieving from database convert the json string to List using Gson.
List<Page> page_list;
Type typeIndicatorForGson = new TypeToken<ArrayList<Page>>() {}.getType();
Gson myGson = new Gson();
page_list = myGson.fromJson(page_json_data_from_database, typeIndicatorForGson);
No List<Object> available on SugarORM. The way you can manage this is a little tricky. In few words, you can manage 1 to N relations, upside down. Take a look to the next example
Lets suppose a Team object which can have N Person objects. Normally you will use a List<Person> in your class Team in this way:
public class Team {
String teamName;
List<Person>
...
}
public class Person {
String name;
String rol;
...
}
Well, it is not possible on SugarORM. But you can add Team as a property in Person, so, any Person's instance should contain a reference to the Team object it belong.
public class Team extends SugarRecord<Team> {
String teamName;
...
}
public class Person extends SugarRecord<Person> {
String name;
String rol;
Team team;
...
}
Then you can get all the Person objects from Team with a method (in the Team class) like:
public class Team extends SugarRecord<Team> {
String teamName;
...
public List<Person> getPersons(){
return Person.find(Person.class, "id = ?", String.valueOf(this.getId()));
}
}
So, you can manage 1 to N relations, but you can't manage N to M relationships (Person belonging to more than one Team object).
IMO the way to manage this is using an Associative Entity in order to split N to M into two 1 to N relationships.
As you can see SugarORM is not allowing you to think just in terms of objects, but, any case you can save a lot of boiler plate coding.
I'm not sure if a class object to transfer data will be more efficient than an object array.
My goal is to know which option is the most efficient and which option is the best practice.
Consider this is a web application served to thousands of users.
Here the two sample cases:
A)
Model.java
public class Model {
public Contact getContact(long id)
{
// some logic
return new Contact(...);
}
}
Contact.java
public class Contact
{
private long id;
private String name;
private String surname;
private String email;
private int session;
private byte[] avatar;
// Constructor
public Contact(long id, String name, ...)
// Getters and Setters
}
B)
Model.java
public class Model {
public Object[] getContact(long id)
{
// some logic
Object[] myReturningContact = new Object[n];
myReturningContact[0] = rs.getLong("id");
// ...
myReturningContact[n] = rs.getBytes("avatar");
return myReturningContact;
}
}
SomeController.java
public class SomeController
{
public void someAction()
{
// Option A
this.setSomeTextTo(contact.getName());
// Option B
this.setSomeTextTo(String.valueOf(returningObject[n]));
}
}
Option A is best practice, unless you have a speed requirement that it can't meet, and Option B can.
Note that Option A will probably be a little faster if you make your fields public and final and don't use getters.
Also note that if you have many primitive fields, the cost of boxing and unboxing will slow down Option B, as may String.valueOf on Strings
I have to implement the following class diagram to the java code. This diagram is very complicated and some parts creates confusion. This question definitely going to help me a lot as well as any reader because it contains several important aspects of UML diagram.
class Book{
String isbn;
String publisher;
String publishDate;
int pages;
}
class BookItem extends Book{
String barcode;
boolean isReferenceOnly;
}
class Author{
String name;
String biography;
Collection<Book> book;
}
class Account{
String number;
List<History> history;
String openDate;
AccountState state;
public Account(AccountState state){
this.state = state;
}
}
enum AccountState{
Active,
Frozen,
Closed
}
class Catalog implements Search, Manage{
List<BookItem> bookItem;
/* Implement the methods of Manage interface */
void add(BookItem item){ }
void remove(BookItem item){ }
/* Implement the methods of Search interface */
int search(BookItem item){ }
}
class Account{
String number;
List<History> history;
Student student = new Student();
void setStudent(Student student){
this.student = student;
}
}
interface Search{
int search(BookItem item);
}
interface Manage{
void add(BookItem item);
void remove(BookItem item);
}
class Student{
String name;
String address;
Search searchBook = new Catalog();
}
class Librarian{
String name;
String address;
String position;
Search searchBook = new Catalog();
Manage manage = new Catalog();
Account account = new Account();
void setAccount(Account account){
this.account = account;
}
class Library{
String name;
String Address;
List<BookItem> bookItem = new ArrayList<BookItem>();
Catalog catalog = new catalog();
List<Account> accounts = new ArrayList<Account>();
Library(Catalog catalog){
this.catalog = catalog;
}
void setBookItem(List<BookItem> bookItem){
this.bookItem = bookItem;
}
void setAccounts(List<Account> accounts){
this.accounts = accounts;
}
}
I implemented in the following way but confusion arise in various cases:
How to implement Class Student use the interface Search.
How to implement Class Librarian use the interfaces Search and Manage.
Why we are not use association instead of usage dependency.
How to implement that Enumeration data type in this case with usage dependency [I have just considered AccountState as a class, i the it is a wrong implementation].
How to use AccountState in the Account [I have just created a object of AccountState].
After read many blogs still unable to implement Aggregation and Composition confidently. Note: In this diagram 3 Aggregations and 1 Composition Exist. Those are:
(a) Library consists of many Account. {Aggregation}
(b) Many Book Item is the part of Library. {Aggregation}
(c) An Account is the part of a Student. {Aggregation}
(d) Library must have a Catalog. {Composition}
Please give your valuable advice so i can learn it well. Thanking you.
Since this question is homework for learning purposes, I will post only examples of how to implement the things you need to review and won't give a direct answer about how to apply them to your current design.
Enumeration in Java is implemented by using enum.
enum WeekDays {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY;
}
Aggregation/Composition means to have a field of the other class. If it's a weak association (aggregation), it should be initialized by the setter or another method. If it's a strong association, it should be initialized in the class constructor since it is needed for the class to live/work.
class WeakAssociation { }
class StrongAssociation { }
class NeedWeekAndStrongAssociation {
private WeakAssociation weakAssociation;
private StrongAssociation strongAssociation;
public NeedWeekAndStrongAssociation(StrongAssociation strongAssociation) {
this.strongAssociation = strongAssociation;
}
public void setWeakAssociation(WeakAssociation weakAssociation) {
this.weakAssociation = weakAssociation;
}
}
Usage dependency means that the class/interface will use the other class/interface within one or more of its methods:
class WantToBeUsed {
public void methodToBeUsed(String data) {
//fancy implementation
}
}
class CannotDoThisAlone {
public void cannotDoItAlone(String data) {
WantToBeUsed wantToBeUsed = new WantToBeUsed();
wantToBeUsed.methodToBeUsed(data);
}
}