Introduction to Multilingual Retrieval Augmented Generation (RAG)
SQLite and ORM Binding - Part 1 - Transcript.pdf
1. SQLite & ORM Binding - Part I
In this module we will persist data locally so we don’t have to go to the server to fetch data every time. We’ll use SQLite not because it’s the best storage medium but
because it’s something I want to teach.
3. public class AppStorage {
private static SQLMap smap;
private static Database db;
private static AppStorage instance = new AppStorage();
private EventDispatcher dispatcher = new EventDispatcher();
private AppStorage() {}
public static AppStorage getInstance() {
initStorage();
return instance;
}
private static void initStorage() {
if(smap == null) {
try {
db = Display.getInstance().openOrCreate("RestDb.sql");
smap = SQLMap.create(db);
AppSettings app = new AppSettings();
Dish dish = new Dish();
smap.setPrimaryKey(app, app.id);
smap.setPrimaryKey(dish, dish.id);
smap.setSqlType(dish.price, SQLMap.SqlType.SQL_DOUBLE);
smap.createTable(app);
smap.createTable(dish);
} catch(IOException err) {
Log.e(err);
}
}
}
AppStorage
App storage abstracts the process of saving the objects, so right now it’s implemented using SQLMap but in theory we could replace that. Let’s review this class
4. public class AppStorage {
private static SQLMap smap;
private static Database db;
private static AppStorage instance = new AppStorage();
private EventDispatcher dispatcher = new EventDispatcher();
private AppStorage() {}
public static AppStorage getInstance() {
initStorage();
return instance;
}
private static void initStorage() {
if(smap == null) {
try {
db = Display.getInstance().openOrCreate("RestDb.sql");
smap = SQLMap.create(db);
AppSettings app = new AppSettings();
Dish dish = new Dish();
smap.setPrimaryKey(app, app.id);
smap.setPrimaryKey(dish, dish.id);
smap.setSqlType(dish.price, SQLMap.SqlType.SQL_DOUBLE);
smap.createTable(app);
smap.createTable(dish);
} catch(IOException err) {
Log.e(err);
}
}
}
AppStorage
The class is implemented as a singleton and initialized on first use
5. public class AppStorage {
private static SQLMap smap;
private static Database db;
private static AppStorage instance = new AppStorage();
private EventDispatcher dispatcher = new EventDispatcher();
private AppStorage() {}
public static AppStorage getInstance() {
initStorage();
return instance;
}
private static void initStorage() {
if(smap == null) {
try {
db = Display.getInstance().openOrCreate("RestDb.sql");
smap = SQLMap.create(db);
AppSettings app = new AppSettings();
Dish dish = new Dish();
smap.setPrimaryKey(app, app.id);
smap.setPrimaryKey(dish, dish.id);
smap.setSqlType(dish.price, SQLMap.SqlType.SQL_DOUBLE);
smap.createTable(app);
smap.createTable(dish);
} catch(IOException err) {
Log.e(err);
}
}
}
AppStorage
Initialization creates the DB instance that is kept within the application. We create the sql mapping instance to the database. Notice that the sqlite API doesn’t have
separate methods for create or open and does the former implicitly which is pretty convenient.
6. public class AppStorage {
private static SQLMap smap;
private static Database db;
private static AppStorage instance = new AppStorage();
private EventDispatcher dispatcher = new EventDispatcher();
private AppStorage() {}
public static AppStorage getInstance() {
initStorage();
return instance;
}
private static void initStorage() {
if(smap == null) {
try {
db = Display.getInstance().openOrCreate("RestDb.sql");
smap = SQLMap.create(db);
AppSettings app = new AppSettings();
Dish dish = new Dish();
smap.setPrimaryKey(app, app.id);
smap.setPrimaryKey(dish, dish.id);
smap.setSqlType(dish.price, SQLMap.SqlType.SQL_DOUBLE);
smap.createTable(app);
smap.createTable(dish);
} catch(IOException err) {
Log.e(err);
}
}
}
AppStorage
Next we create the tables if they don’t exist. We create blank object instances for the objects that we need, then define the primary key field.
I also set the column type for price to be double, this is no longer necessary as newer versions of the SQLMap API detect the DoubleProperty and automatically set the
right type. Create table literally issues a create statement, notice that we don’t support alter at this time although this might be introduced in the future.
7. public void insert(PropertyBusinessObject d) {
try {
smap.insert(d);
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error saving: " + err);
}
}
public void update(PropertyBusinessObject d) {
try {
smap.update(d);
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error updating storage: " + err);
}
}
public void addDeleteListener(ActionListener<ActionEvent> onDelete) {
dispatcher.addListener(onDelete);
}
public void removeDeleteListener(ActionListener<ActionEvent> onDelete) {
dispatcher.removeListener(onDelete);
}
AppStorage
Insert and update are pretty much direct wrappers to the builtin methods that handle the exceptions locally instead of propagating the IOException from the sqlite API
8. public void insert(PropertyBusinessObject d) {
try {
smap.insert(d);
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error saving: " + err);
}
}
public void update(PropertyBusinessObject d) {
try {
smap.update(d);
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error updating storage: " + err);
}
}
public void addDeleteListener(ActionListener<ActionEvent> onDelete) {
dispatcher.addListener(onDelete);
}
public void removeDeleteListener(ActionListener<ActionEvent> onDelete) {
dispatcher.removeListener(onDelete);
}
AppStorage
The reason for the delete listener is that we want the dish list to automatically remove the dish when it’s deleted by the edit form. This allows us to decouple that code
and get an event from the underlying data model.
We use the event dispatcher class to subscribe and fire an event here, this is a really convenient class when you are building an API
9. public void delete(PropertyBusinessObject d) {
try {
smap.delete(d);
dispatcher.fireActionEvent(new ActionEvent(d));
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error deleting: " + err);
}
}
public AppSettings fetchAppSettings() {
try {
AppSettings a = new AppSettings();
a.name.set(null);
a.tagline.set(null);
List<PropertyBusinessObject> lp = smap.select(a, null, true, 1000, 0);
if(lp.size() == 0) {
a = new AppSettings();
a.id.set("1");
insert(a);
return a;
}
return (AppSettings)lp.get(0);
} catch(Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading AppSettings: " + err);
return null;
}
AppStorage
Delete serves pretty much the same purpose as update and insert with the exception that it also fires the dispatch event appropriately
10. public void delete(PropertyBusinessObject d) {
try {
smap.delete(d);
dispatcher.fireActionEvent(new ActionEvent(d));
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error deleting: " + err);
}
}
public AppSettings fetchAppSettings() {
try {
AppSettings a = new AppSettings();
a.name.set(null);
a.tagline.set(null);
List<PropertyBusinessObject> lp = smap.select(a, null, true, 1000, 0);
if(lp.size() == 0) {
a = new AppSettings();
a.id.set("1");
insert(a);
return a;
}
return (AppSettings)lp.get(0);
} catch(Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading AppSettings: " + err);
return null;
}
AppStorage
App Settings is a special object as there is only one of it. So when we issue a select query we should never expect more than one entry. However it’s still a select query…
By default when we invoke the select method if a property value isn’t null it’s added to the where clause. Both name and tagline default to non-null values and so we
need to set them to null. Once that’s done we can just do the select query as usual.
11. public void delete(PropertyBusinessObject d) {
try {
smap.delete(d);
dispatcher.fireActionEvent(new ActionEvent(d));
} catch(IOException err) {
Log.e(err);
ToastBar.showErrorMessage("Error deleting: " + err);
}
}
public AppSettings fetchAppSettings() {
try {
AppSettings a = new AppSettings();
a.name.set(null);
a.tagline.set(null);
List<PropertyBusinessObject> lp = smap.select(a, null, true, 1000, 0);
if(lp.size() == 0) {
a = new AppSettings();
a.id.set("1");
insert(a);
return a;
}
return (AppSettings)lp.get(0);
} catch(Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading AppSettings: " + err);
return null;
}
AppStorage
If there are no entries we just invoke insert for a new settings object.
12. public List<PropertyBusinessObject> fetchDishes() {
try {
Dish d = new Dish();
List<PropertyBusinessObject> lp = smap.select(d, d.name, true, 1000, 0);
// workaround for null images
for(PropertyBusinessObject p : lp) {
Dish dd = (Dish)p;
if(dd.getFullSize() == null) {
dd.setFullSize(Resources.getGlobalResources().getImage("food1.jpg"));
}
}
return lp;
} catch(Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading dishes: " + err);
return null;
}
}
AppStorage
Fetching the list of dishes is similar but more standard as we return the full list of dishes. The main point of interest is the use of the name property to indicate the order
by option for the select statement.
13. public List<PropertyBusinessObject> fetchDishes() {
try {
Dish d = new Dish();
List<PropertyBusinessObject> lp = smap.select(d, d.name, true, 1000, 0);
// workaround for null images
for(PropertyBusinessObject p : lp) {
Dish dd = (Dish)p;
if(dd.getFullSize() == null) {
dd.setFullSize(Resources.getGlobalResources().getImage("food1.jpg"));
}
}
return lp;
} catch(Exception err) {
Log.e(err);
ToastBar.showErrorMessage("Error loading dishes: " + err);
return null;
}
}
AppStorage
Some dishes might not have an image within them as we create them and here we force a placeholder image to prevent such a case. I would recommend that you
always use that strategy and have a good looking placeholder image.
Now that we have the basic persistence object in place lets proceed to integration but first let’s discuss a conceptual issue. Why do we need persistence to begin with?