Android Database Framework - Green Dao Lightweight Object Relational Mapping Framework, Permanent Goodbye to sqlite
Not long ago, I wrote a blog post on ORMLite framework.
But for me personally, I may be more inclined to use Green Dao, so today I have brought you a detailed blog of Green Dao. I hope you like it. I also introduced ORM's ideas in detail before. If you don't understand, you can read the previous blog first. Here we will not go into details. We will build a new project.
I. Relevant Introduction
- Official website: http://greendao-orm.com/
- Github : https://github.com/greenrobot/greenDAO
Legendary Advantages
- Maximum performance (fastest Android ORM)
- Easy to use API
- Highly optimized
- Minimum memory consumption
Let's see how to integrate first.
- Integrated documents: http://greenrobot.org/greendao/documentation/how-to-get-started/
First, let's look at the integration steps on Github and add dependencies
We want to add
compile 'org.greenrobot:greendao:2.2.1'
- 1
- 1
Also add Java Project package
compile 'org.greenrobot:greendao-generator:2.2.0'
- 1
- 1
Next, we create a folder java-gen in the main directory
Then continue to add in the configuration file
//Warehouse
sourceSets{
main{
java.srcDirs = ['src/main/java','src/main/java-gen']
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
Finally, create a new Module and select Java Library, after successful creation, we add it under his build.gradle
compile 'org.greenrobot:greendao-generator:2.2.0'
- 1
- 1
II. Entity Class
At this point, our preliminary integration is completed, is it more troublesome, let's see?
She said we're going to see that java-gen has generated code, but actually we're not, because we need NoteDao.java, a data caching object, so what do we need to do? We wrote it directly in Modal's class
package com.example;
import de.greenrobot.daogenerator.DaoGenerator;
import de.greenrobot.daogenerator.Entity;
import de.greenrobot.daogenerator.Schema;
public class DaoMaker {
public static void main(String[] args) {
//Generate database entity classes and version numbers
Schema schema = new Schema(1, "com.student.entity");
addStudent(schema);
//Specify dao
schema.setDefaultJavaPackageDao("com.student.dao");
try {
//Specified path
new DaoGenerator().generateAll(schema, "D:\\github\\GreenDao\\app\\src\\main\\java-gen");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Create tables for databases
*
* @param schema
*/
public static void addStudent(Schema schema) {
//Create tables for databases
Entity entity = schema.addEntity("Student");
//The primary key is the int type
entity.addIdProperty();
//Name
entity.addStringProperty("name");
//Age
entity.addIntProperty("age");
//address
entity.addStringProperty("address");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
At this time we have to pay attention to, we can not go directly to the project, we have to compile this java class separately. Right-click
The compilation time is a bit long, so we can wait patiently. At this time, we can see that the console will print the relevant information.
If you look at the java-gen directory at this time, you'll have something.
Very nice. It's almost finished in the early stage. We can see his prototype.
III. Core Categories
As you can see, GreenDao has classes created by itself. Let's see what classes it is.
-
DaoSession: The session layer, which operates on specific dDao classes, provides basic persistence operations, such as insert,load,update,refresh,delete operations on entity objects.
-
XXDao: Actually generated Dao classes usually correspond to specific java classes. greenDao creates a Dao for each entity class. It provides a more specific payment than DaoSession, such as count,loadALL and inserlnTx, for bulk insertion.
-
xxEntity: A persistent entity object, usually representing data base java attributes of row standard
- Schema: Instance data schema that invokes the constructor through the schema version and default java packages
IV. Packaging Operations Class
OK, that's almost all. Let's do it in real combat. In actual combat, we don't need to put too much logic code in the main Activity. It's actually the best to encapsulate all the logic code in an operation class of a database. So let's create a new class first.
package com.lgl.greendao;
import android.content.Context;
import com.student.dao.DaoMaster;
import com.student.dao.DaoSession;
import de.greenrobot.dao.query.QueryBuilder;
/**
* Database Operations Class
* Created by LGL on 2016/7/2.
*/
public class DaoManager {
/**
* Implementing Functions
* 1.Create a database
* 2.Create tables for databases
* 3.Upgrade of database
* 4.Addition, deletion and modification of database
*/
//TAG
public static final String TAG = DaoManager.class.getSimpleName();
//Database name
private static final String DB_NAME = "greendao.db";
//Multithread access
private volatile static DaoManager manager;
//Operational class
private static DaoMaster.DevOpenHelper helper;
//context
private Context mContext;
//Core Categories
private static DaoMaster daoMaster;
private DaoSession daoSession;
//Singleton pattern
public static DaoManager getInstance() {
DaoManager instance = null;
if (manager == null) {
synchronized (DaoManager.class) {
if (instance == null) {
instance = new DaoManager();
manager = instance;
}
}
}
return instance;
}
//Passing context
public void initManager(Context context){
this.mContext = context;
}
/**
* Determine whether a database exists, and if not, create it
*
* @return
*/
public DaoMaster getDaoMaster() {
if (daoMaster == null) {
helper = new DaoMaster.DevOpenHelper(mContext, DB_NAME, null);
daoMaster = new DaoMaster(helper.getWritableDatabase());
}
return daoMaster;
}
/**
* Complete the operation of the database, just an interface
*
* @return
*/
public DaoSession getDaoSession() {
if (daoSession == null) {
if (daoMaster == null) {
daoMaster = getDaoMaster();
}
daoSession = daoMaster.newSession();
}
return daoSession;
}
/**
* Open the output log and turn it off by default
*/
public void setDebug() {
QueryBuilder.LOG_SQL = true;
QueryBuilder.LOG_VALUES = true;
}
/**
* Close DaoSession
*/
public void closeDaoSession() {
if (daoSession != null) {
daoSession.clear();
daoSession = null;
}
}
/**
* Close Helper
*/
public void closeHelper() {
if (helper != null) {
helper.close();
helper = null;
}
}
/**
* Close all operations
*/
public void closeConnection() {
closeHelper();
closeDaoSession();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
This class can initialize many operations of the database, but that's not enough. We need to write a real operation class. Now we just implement an insert action.
package com.lgl.greendao;
import android.content.Context;
import com.student.entity.Student;
/**
* Complete specific operations on a table
* Created by LGL on 2016/7/2.
*/
public class CommonUtils {
private DaoManager daoManager;
//Construction method
public CommonUtils(Context context) {
daoManager = DaoManager.getInstance();
daoManager.initManager(context);
}
/**
* Insertion of student tables in Databases
* @param student
* @return
*/
public boolean insertStudent(Student student) {
boolean flag = false;
flag = daoManager.getDaoSession().insert(student) != -1 ? true : false;
return flag;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
V. Insertion
OK, let's first look at how sql inserts are done, define a button
<Button
android:id="@+id/btn_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Add data" />
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
We just need to watch his clicks.
case R.id.btn_add:
dbUtils = new CommonUtils(this);
Student student = new Student();
student.setName("lgl");
student.setAge(18);
student.setAddress("Beijing");
dbUtils.insertStudent(student);
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Once we run it, we can find the database in data/data/registration/database.
Just put him everywhere on the desktop and you can view the data.
It doesn't feel good here. ID should be set up to grow automatically, right? In fact, as long as the ID is not set, he will grow by himself.
VI. Batch insertion
Insertion is OK, so we can add a method to Common Utils.
/**
* Batch insertion
*
* @param students
* @return
*/
public boolean inserMultStudent(final List<Student> students) {
//Identification
boolean flag = false;
try {
//Insertion operation time-consuming
daoManager.getDaoSession().runInTx(new Runnable() {
@Override
public void run() {
for (Student student : students) {
daoManager.getDaoSession().insertOrReplace(student);
}
}
});
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
Let's go and verify it. Let's write a button first.
<Button
android:id="@+id/btn_add_mult"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Batch Adding Data" />
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
In this way, we can see his click event directly.
case R.id.btn_add_mult:
List<Student> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Student studentlist = new Student();
studentlist.setName("lgl" + i);
studentlist.setAge(18 + i);
studentlist.setAddress("Beijing" + i);
list.add(studentlist);
}
dbUtils.inserMultStudent(list);
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
Now let's export the database.
OK, this is very nice.
VII. Amendment
Add, delete, check and correct, add and finish, let's revise, we continue to write the method of modification and then verify it. This is also a good way for readers to read.
/**
* modify
* @param student
* @return
*/
public boolean updateStudent(Student student){
boolean flag = false;
try{
daoManager.getDaoSession().update(student);
flag = true;
}catch (Exception e){
e.printStackTrace();
}
return flag;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
OK, let's go and verify it. Write a button?
<Button
android:id="@+id/btn_update"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Modify a piece of data" />
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
Look directly at click events
case R.id.btn_update:
Student studentupdate = new Student();
//Modify according to ID
studentupdate.setId((long) 1);
//Change the age to 100
studentupdate.setAge(100);
dbUtils.updateStudent(studentupdate);
break;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Very good, very strong. Let's export the database and see if it's changed to 100 years old.
Okay, so we can see that although it's changed to 100, the other fields are null because we didn't set them when we changed them. Okay, set them up.
Now we can see the changes.
VIII. Delete
It's easier to delete. Let's write it directly.
/**
* delete
*
* @param student
* @return
*/
public boolean deleteStudent(Student student) {
boolean flag = false;
try {
//Delete the specified ID
daoManager.getDaoSession().delete(student);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
So write a button.
<Button
android:id="@+id/btn_delete"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Delete a piece of data" />
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
OK, click events are relatively simple
case R.id.btn_delete:
Student studentdelete = new Student();
studentdelete.setId((long) 3);
dbUtils.deleteStudent(studentdelete);
break;
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
What we want to delete is the data whose ID is 3, so we run it.
As you can see, the data with ID 3 is missing.
9. Query
OK, add, delete, check and modify, we only have to query. Let's first look at how to query a statement and write a method.
/**
* Query sheet
*
* @param key
* @return
*/
public Student listOneStudent(long key) {
return daoManager.getDaoSession().load(Student.class, key);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
Let's define another button
<Button
android:id="@+id/btn_load"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Query a piece of data" />
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
So we can check it out.
case R.id.btn_load:
Log.i(TAG, dbUtils.listOneStudent(5) + "");
break;
- 1
- 2
- 3
- 1
- 2
- 3
10. All inquiries
All queries are relatively simple
/**
* All queries
*
* @return
*/
public List<Student> listAll() {
return daoManager.getDaoSession().loadAll(Student.class);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
Just call it directly.
case R.id.btn_load_all:
List<Student> lists = dbUtils.listAll();
Log.i(TAG, lists.toString());
break;
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
11. Query Builder
As the most important query, GreenDao must have done a lot for us. We can look through the API to find Query Builder. How can we use this composite query? We do logical queries according to conditions, native, ORMLite and GreenDao, so I'll compare native and GreenDao.
/**
* Native Query
*/
public void queryNative() {
//query criteria
String where = "where name like ? and _id > ?";
//Query with sql
List<Student> list = daoManager.getDaoSession().queryRaw(Student.class, where,
new String[]{"%l%", "6"});
Log.i(TAG, list + "");
}
/**
* QueryBuilder Queries larger than
*/
public void queryBuilder() {
//Query builder
QueryBuilder<Student> queryBuilder = daoManager.getDaoSession().queryBuilder(Student.class);
//Beijing older than 19
List<Student> list = queryBuilder.where(StudentDao.Properties.Age.ge(19)).where(StudentDao.Properties.Address.like("Beijing")).list();
Log.i(TAG, list + "");
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
It is not difficult to see that it is more convenient to use, we can infinitely add where conditional query.
12. End
We have basically finished adding, deleting, checking and modifying, and basically the framework is OK. Let's take a look at the screenshot of his operation.
The logic is fairly detailed, but this Common Utils is scattered.
CommonUtils
package com.lgl.greendao;
import android.content.Context;
import android.util.Log;
import com.student.dao.StudentDao;
import com.student.entity.Student;
import java.util.List;
import de.greenrobot.dao.query.QueryBuilder;
/**
* Complete specific operations on a table
* Created by LGL on 2016/7/2.
*/
public class CommonUtils {
//TAG
private static final String TAG = CommonUtils.class.getSimpleName();
private DaoManager daoManager;
//Construction method
public CommonUtils(Context context) {
daoManager = DaoManager.getInstance();
daoManager.initManager(context);
}
/**
* Insertion of student tables in Databases
*
* @param student
* @return
*/
public boolean insertStudent(Student student) {
boolean flag = false;
flag = daoManager.getDaoSession().insert(student) != -1 ? true : false;
return flag;
}
/**
* Batch insertion
*
* @param students
* @return
*/
public boolean inserMultStudent(final List<Student> students) {
//Identification
boolean flag = false;
try {
//Insertion operation time-consuming
daoManager.getDaoSession().runInTx(new Runnable() {
@Override
public void run() {
for (Student student : students) {
daoManager.getDaoSession().insertOrReplace(student);
}
}
});
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* modify
*
* @param student
* @return
*/
public boolean updateStudent(Student student) {
boolean flag = false;
try {
daoManager.getDaoSession().update(student);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
/**
* delete
*
* @param student
* @return
*/
public boolean deleteStudent(Student student) {
boolean flag = false;
try {
//Delete the specified ID
daoManager.getDaoSession().delete(student);
flag = true;
} catch (Exception e) {
e.printStackTrace();
}
//daoManager.getDaoSession().deleteAll(); //delete all records
return flag;
}
/**
* Query sheet
*
* @param key
* @return
*/
public Student listOneStudent(long key) {
return daoManager.getDaoSession().load(Student.class, key);
}
/**
* All queries
*
* @return
*/
public List<Student> listAll() {
return daoManager.getDaoSession().loadAll(Student.class);
}
/**
* Native Query
*/
public void queryNative() {
//query criteria
String where = "where name like ? and _id > ?";
//Query with sql
List<Student> list = daoManager.getDaoSession().queryRaw(Student.class, where,
new String[]{"%l%", "6"});
Log.i(TAG, list + "");
}
/**
* QueryBuilder Queries larger than
*/
public void queryBuilder() {
//Query builder
QueryBuilder<Student> queryBuilder = daoManager.getDaoSession().queryBuilder(Student.class);
//Beijing older than 19
List<Student> list = queryBuilder.where(StudentDao.Properties.Age.ge(19)).where(StudentDao.Properties.Address.like("Beijing")).list();
Log.i(TAG, list + "");
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
OK, this blog is over. Let's talk about it in the next article.