为什么要用ORM
在开发Android的过程中,我们基本上都会用到sqlite数据库,一般省事的情况下都是使用网上的一些orm框架操作sqlite数据库,比较有名的ORMLite、GreenDao和Active Android等。一般情况下,当预料到数据库会复杂到某个程度,就有必要引入数据库的ORM框架,这样可以大大降低开发和维护的成本。当然,站在巨人的肩膀上,选择好的开源库更可以事半功倍。
选择怎么样的ORM
Android上的ORM框架不少:GreenDao, Active Android,Schematic,Ollie,Sprinkles...到底应该如何选择?这些ORM框架用到的实现方式主要有几种:1.注解(运行时 Annotation,编译时 Annotation)2.反射3.直接生成代码这里简单地直接说结论:通过运行时注解或反射去建立数据表和实体的关系会导致性能比较低。从性能角度出发,应该选择使用编译时注解或代码生成的框架。当然,成熟程度,文档资料等也是考量点。
感兴趣的朋友可以自己去试试,今天我要说的是自己在android sdk 为我们提供的基础上进行自己的封装,达到方便高效的目的。
Android提供了一个SQLiteOpenHelper类来可以很方便的操作数据库,继承和扩展SQLiteOpenHelper类主要做的工作就是重写以下两个方法。
onCreate: 当数据库被首次创建时执行该方法,一般将创建表等初始化操作在该方法中执行。
onUpgrade:当打开数据库时传入的版本号与当前的版本号不同时会调用该方法。
想要封装自己的数据库操作类就要继承SQLiteOpenHelper,到达自己的目的了,首先看下我写的。
- public class DBHelper extends SQLiteOpenHelper{
- private static Logger logger = LoggerFactory.getLogger(DBHelper.class);
- private final static String DATABASE_PATH = Environment.getExternalStorageDirectory() + "/";
- private Context context;
- private int dbVersion;
- public DBHelper(Context context, String dbName, int initVersion) {
- super(context, dbName, (SQLiteDatabase.CursorFactory)null, initVersion);
- this.context = context;
- this.dbVersion = initVersion;
- }
- @Override
- public void onCreate(SQLiteDatabase db) {
- for(int i = 1; i <= this.dbVersion; ++i) {
- this.upgradeSqlData(db, i, false);
- }
- }
- private void upgradeSqlData(SQLiteDatabase db, int version, boolean isDowngrade) {
- String fileName;
- if(isDowngrade) {
- fileName = "_" + version + "_db.sql";
- } else {
- fileName = version + "_db.sql";
- }
- BufferedReader bufferedReader = null;
- db.beginTransaction();
- try {
- InputStream e = this.context.getAssets().open(fileName);
- InputStreamReader reader = new InputStreamReader(e, "UTF-8");
- bufferedReader = new BufferedReader(reader);
- String line = bufferedReader.readLine();
- StringBuilder sb = new StringBuilder();
- while(true) {
- while(line != null) {
- line = line.trim();
- if(!"".equals(line) && line.charAt(0) != 47 && line.charAt(0) != 45) {
- boolean middleIndex = true;
- int middleIndex1;
- if((middleIndex1 = line.lastIndexOf("--")) != -1) {
- line = line.substring(0, middleIndex1);
- }
- sb.append(line);
- String sql = sb.toString();
- if(!"".equals(sql) && sql.charAt(sql.length() - 1) == 59) {
- logger.debug("load sql:{}", sql);
- db.execSQL(sql.substring(0, sql.indexOf(59)));
- sb = new StringBuilder();
- }
- line = bufferedReader.readLine();
- } else {
- line = bufferedReader.readLine();
- }
- }
- db.setTransactionSuccessful();
- logger.info("load file {} success.", fileName);
- Log.e("TAG","load file {} success." + fileName);
- break;
- }
- } catch (Exception var20) {
- logger.error("load file {} failed.", fileName, var20);
- Log.e("TAG","load file {} success." + fileName);
- } finally {
- try {
- if(bufferedReader != null) {
- bufferedReader.close();
- }
- db.endTransaction();
- } catch (IOException var19) {
- ;
- }
- }
- }
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- for(int i = oldVersion + 1; i <= newVersion; ++i) {
- this.upgradeSqlData(db, i, false);
- }
- }
- public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- for(int i = oldVersion - 1; i >= newVersion; --i) {
- this.upgradeSqlData(db, i, false);
- }
- }
- // @Override
- // public SQLiteDatabase getWritableDatabase() {
- // File file = new File(DATABASE_PATH + "dbtest");
- // if (!file.exists()) {
- // file.mkdirs();
- //
- // }
- // File dbFile = new File(file,"mydb.db");
- // if (!dbFile.exists())
- // try {
- // dbFile.createNewFile();
- // } catch (IOException e) {
- // e.printStackTrace();
- // }
- // return SQLiteDatabase.openOrCreateDatabase(dbFile,null);
- // }
- //
- // @Override
- // public SQLiteDatabase getReadableDatabase() {
- // return getWritableDatabase();
- // }
- }
重写了oncreate()方法和onUpgrade()方法,前面也说了两个方法的用途,数据库初次创建的时候,肯定是要建数据库建表,所以在onCreate()的方法执行了创建表的sql语句,
因为默认了数据库的创建路径,所以我们是不需要手动的创建数据库文件的,如果你想制定数据库创建的路径,那么就得自己创建数据库文件了,类中注释掉的代码就是指定在sdcard中创建数据库文件的代码。再说一说sql建表语句,我将建表的sql语句写到了android目录下的assets文件夹下面,在该文件夹下新建一个文本文件,后缀改为.sql其它格式也行,但是要注意自己代码中要一致。
- -- Describe USER
- CREATE TABLE cdd_user (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_name TEXT, --对应userCode
- user_age TEXT --对应userName
- );
这里要说明一下,这个.sql文件的命名是要有一定规则的,以数字开头然后下划线然后加上db,如 1_db.sql 当然这只是我自己定义的规则方便读取,你们也可以定义自己的规则,但是DBHelper中的方法就得自己修改了,不然是找不到文件的。1_db.sql的1代表了当前的数据库版本号是1,每次相对于上个版本增加了表就新增加一个sql文件,命名前的数字比上次的sql文件加1,这样数据库就能自动创建表新增表了,而不需要我们删除app再安装,这就是数据库版本号的用处。
下面就说说数据库dao,android sdk已经封装好了插入,删除,更新,查询方法,但是这样每次都写一大堆东西太冗余了,我们简单的封装一下就好多了,首先写个基类其中包含增删改查。
- public class BaseRepository<T> {
- protected Logger logger = LoggerFactory.getLogger(this.getClass());
- protected DBHelper dbHelper;
- public BaseRepository(Context context) {
- dbHelper = DBUtil.getInstance(context).getDbHelper();
- }
- /**
- * 数据插入
- * @param table
- * @param nullColumnHack
- * @param values
- * @return
- */
- public long insert(String table, String nullColumnHack, ContentValues values) {
- long ret = 0;
- SQLiteDatabase database = dbHelper.getWritableDatabase();
- database.beginTransaction();
- try {
- ret = database.insert(table, nullColumnHack, values);
- database.setTransactionSuccessful();
- } catch (RuntimeException e) {
- logger.error("insert error. ", e);
- } finally {
- database.endTransaction();
- }
- return ret;
- }
- /**
- * 数据查询
- * @param table
- * @param columns
- * @param selection
- * @param selectionArgs
- * @param groupBy
- * @param having
- * @param orderBy
- * @param limit
- * @param <T>
- * @return
- */
- public <T> List<T> query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, Integer limit) {
- List<T> results = new ArrayList<T>();
- Cursor cursor = null;
- try {
- if (limit != null) {
- cursor = dbHelper.getReadableDatabase().query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit + "");
- } else {
- cursor = dbHelper.getReadableDatabase().query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
- }
- results = queryResult(cursor);
- } catch (RuntimeException e) {
- logger.error("query error. ", e);
- } finally {
- if (cursor != null) {
- cursor.close();
- }
- }
- return results;
- }
- /**
- * 转换为对象
- * @param cursor
- * @param <T>
- * @return
- */
- public <T> List<T> queryResult(Cursor cursor) {
- throw new RuntimeException("Please overwrite method.");
- }
- /**
- * 数据更新
- * @param table
- * @param values
- * @param whereClause
- * @param whereArgs
- * @return
- */
- public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
- int ret = 0;
- SQLiteDatabase database = dbHelper.getWritableDatabase();
- database.beginTransaction();
- try {
- ret = database.update(table, values, whereClause, whereArgs);
- database.setTransactionSuccessful();
- } catch (RuntimeException e) {
- logger.error("update error. ", e);
- } finally {
- database.endTransaction();
- }
- return ret;
- }
- /**
- * 数据删除
- * @param table
- * @param whereClause
- * @param whereArgs
- * @return
- */
- public int delete(String table, String whereClause, String[] whereArgs) {
- int ret = 0;
- SQLiteDatabase database = dbHelper.getWritableDatabase();
- database.beginTransaction();
- try {
- ret = database.delete(table, whereClause, whereArgs);
- database.setTransactionSuccessful();
- } catch (RuntimeException e) {
- logger.error("delete error. ", e);
- } finally {
- database.endTransaction();
- }
- return ret;
- }
- }
后面所有的dao都继承该基类,传入泛型就得到了我们想要的,下面给个User的dao例子
- public class UserRepository extends BaseRepository<User>{
- public UserRepository(Context context) {
- super(context);
- }
- /**
- * 插入数据
- * @param user
- * @return
- */
- public long insert(User user) {
- ContentValues values = getContentValues(user);
- values.remove("id");
- return insert(DBConstant.TABLE_USER,null,values);
- }
- /**
- * 更新数据
- * @param user
- */
- public void update(User user) {
- ContentValues cv = getContentValues(user);
- cv.remove("id");
- update(DBConstant.TABLE_USER,cv,"id = ?",new String[]{user.getId()+""});
- }
- /**
- * 删除数据
- * @param id
- */
- public void delete(int id) {
- delete(DBConstant.TABLE_USER,"id = ?",new String[]{id+""});
- }
- /**
- * 查询所有数据
- * @return
- */
- public List<User> readAllUsers() {
- List<User> list = query(DBConstant.TABLE_USER,null,null, null, null, null, null,null);
- if(list != null && !list.isEmpty())
- {
- return list;
- }
- return null;
- }
- @Override
- public List<User> queryResult(Cursor cursor) {
- List<User> userList = new ArrayList<User>();
- User user;
- while (cursor.moveToNext()) {
- user = new User();
- user.setId(cursor.getInt(cursor.getColumnIndex("id")));
- user.setUserName(cursor.getString(cursor.getColumnIndex("user_name")));
- user.setAge(cursor.getInt(cursor.getColumnIndex("user_age")));
- userList.add(user);
- }
- return userList;
- }
- private ContentValues getContentValues(User user) {
- ContentValues cv = new ContentValues();
- cv.put("id",user.getId());
- cv.put("user_name",user.getUserName());
- cv.put("user_age",user.getAge());
- return cv;
- }
嗯,看上去简单清爽了很多,需要重写的代码也没有几个,主要还是bean和cursor的转换,对数据库不太了解的朋友可以去查查相关的资料,这里就不做基本知识的普及了。
最后将DBHelper做成单例模式,也贴下代码
- public class DBUtil {
- private static DBUtil instance;
- private static DBHelper dbHelper;
- private DBUtil() {
- }
- public static DBUtil getInstance(Context context) {
- if (instance == null) {
- instance = new DBUtil();
- initDBHelper(context);
- }
- return instance;
- }
- private static void initDBHelper(Context context) {
- dbHelper = new DBHelper(context, DBConstant.DB_NAME,DBConstant.DB_INIT_VERSION);
- dbHelper.onCreate(dbHelper.getWritableDatabase());
- }
- public DBHelper getDbHelper(){
- return dbHelper;
- }
- }