在android中使用原始的SQLiteOpenHelper操作数据库显得过于繁琐,而且对于不是很熟悉数据库操作的人来说比较容易出现一些隐藏的漏洞。所以一般都会想到使用相关的ORMLite框架完成开发,类似于J2EE开发中的Hibernate和Mybatis等等,在提高开发效率的同时,也可以有效避免数据库操作对应用带来的潜在影响。
到现在为止,Android中ORM框架也已经有很多,比如ORMLite,Litepal, androrm,SugarORM, GreenDAO,ActiveAndroid, Realm等等。对于他们之间的对比,可能各有长短,所谓存在即为合理。其中,ORMLite应该是使用较为广泛的一个,接下来我将通过几篇文章,结合ORMLIte的官方文档和源代码对这个框架进行分析。才疏学浅,如果有不足的地方,还请批评指正。
ORMLite官网: http://ormlite.com/,下载jar包和实例。将jar包加入项目中。
第一篇,我先结合官方实例和自己的demo让大家感受一下ORMLite的魅力,并熟悉整个流程。
尊重原创,转载请说明出处,谢谢! http://blog.csdn.net/oyangyujun
一、定义实体类
1. 注解属性和类名,对应数据库字段和表明。
2. 给定一个无参构造函数,以便查询返回实体对象。
@DatabaseTable(tableName = "person")
public class Person implements Serializable{
private static final long serialVersionUID = 1L;
@DatabaseField(generatedId = true)
int id;
@DatabaseField(canBeNull = true, defaultValue = "name")
String name;
@DatabaseField(canBeNull = true, defaultValue = "sex")
String sex;
@DatabaseField(canBeNull = true, defaultValue = "age")
String age;
@DatabaseField(canBeNull = true, defaultValue = "address")
String address;
@DatabaseField(canBeNull = true, defaultValue = "phone")
String phone;
@DatabaseField(canBeNull = true, defaultValue = "qq")
String qq;
@DatabaseField(canBeNull = true, defaultValue = "testField")
String testField;
@DatabaseField(canBeNull = true, defaultValue = "testField2")
String testField2;
public Person(){
}
二、生成数据库配置文件
1. 先在res/raw下创建文件ormlite_config.txt
2. 继承OrmLiteCongifUtil类创建DatabaseConfigUtil工具了类,这个工具类用于生成数据库结构信息。
public class DatabaseConfigUtil extends OrmLiteConfigUtil {
public static void main(String[] args) throws SQLException, IOException {
writeConfigFile("ormlite_config.txt");
}
}
3. 在java本地环境下运行该类,不能直接运行android项目。本地环境配置的方法是,右键-》Run Configurations进入运行配置面板如下,注意看是否为当前项目的该工具类。
4. 选择JRE,选中Alternate JRE,指定使用的JRE版本,官方文档中说1.5或者1.6,当然,高版本也是可以的。
5. 选择Classpath,选中Bootstrap Entries下的android,remove掉。切记保留User Entries下的文件。否则会报NoClassDefFoundError, 这里其实就是取消android应用程序的入口,直接将上面的工具类作为程序入口。
6. 最后直接run,运行完成后会在ormlite_config.txt中生成下面的配置文件内容。
这个文件是数据库升级更新的依据。这样做的原因是,运行时注解是非常号资源的过程,程序运行时通过反射获取数据表结构维护数据库信息会严重影响效率,虽然OrmLite说明其注解比Java自身的注解机制速度提高了近20倍,不过还是推荐使用这种配置文件的方式。
三、创建数据库辅助类OrmLiteSqliteOpenHelper
1. 创建OrmLite数据库的方式和通过SqliteOpenHelper的维护数据库的方式基本相同,因为OrmLiteSqliteOpenHelper是SqliteOpenHelper的直接子类。我们在项目中使用时需要继承OrmLiteSqliteOpenHelper,重写onCreate和onUpgrade方法。不过OrmLiteSqliteOpenHelper类中这两个方法都比SqliteOpenHelper中多了一个ConnectionSource参数。从字面量理解这个参数主要是用于数据库的升级更新和创建DAO。注意,每次数据模型有变化是,都必须运行OrmLiteCongifUtil工具类更新配置文件和升级数据库版本号,并在onUpgrade中完成相关操作。
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
// name of the database file for your application -- change to something appropriate for your app
private static final String DATABASE_NAME = "helloAndroid.db";
// any time you make changes to your database objects, you may have to increase the database version
private static final int DATABASE_VERSION = 3;
// the DAO object we use to access the SimpleData table
private Dao<SimpleData, Integer> simpleDao = null;
private RuntimeExceptionDao<SimpleData, Integer> simpleRuntimeDao = null;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config);
}
/**
* This is called when the database is first created. Usually you should call createTable statements here to create
* the tables that will store your data.
*/
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
Log.i(DatabaseHelper.class.getName(), "onCreate");
TableUtils.createTable(connectionSource, SimpleData.class);
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
throw new RuntimeException(e);
}
// here we try inserting data in the on-create as a test
RuntimeExceptionDao<SimpleData, Integer> dao = getSimpleDataDao();
long millis = System.currentTimeMillis();
// create some entries in the onCreate
SimpleData simple = new SimpleData(millis);
dao.create(simple);
simple = new SimpleData(millis + 1);
dao.create(simple);
Log.i(DatabaseHelper.class.getName(), "created new entries in onCreate: " + millis);
}
/**
* This is called when your application is upgraded and it has a higher version number. This allows you to adjust
* the various data to match the new version number.
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
try {
Log.i(DatabaseHelper.class.getName(), "onUpgrade");
TableUtils.dropTable(connectionSource, SimpleData.class, true);
// after we drop the old databases, we create the new ones
onCreate(db, connectionSource);
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't drop databases", e);
throw new RuntimeException(e);
}
}
/**
* Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
* value.
*/
public Dao<SimpleData, Integer> getDao() throws SQLException {
if (simpleDao == null) {
simpleDao = getDao(SimpleData.class);
}
return simpleDao;
}
/**
* Returns the RuntimeExceptionDao (Database Access Object) version of a Dao for our SimpleData class. It will
* create it or just give the cached value. RuntimeExceptionDao only through RuntimeExceptions.
*/
public RuntimeExceptionDao<SimpleData, Integer> getSimpleDataDao() {
if (simpleRuntimeDao == null) {
simpleRuntimeDao = getRuntimeExceptionDao(SimpleData.class);
}
return simpleRuntimeDao;
}
/**
* Close the database connections and clear any cached DAOs.
*/
@Override
public void close() {
super.close();
simpleDao = null;
simpleRuntimeDao = null;
}
}
表升级对数据库数据的影响怎么解决?目前是什么情况?
四、获得DAO对象
获得DAO的方式有两种。
1. 通过OrmLiteSqliteOpenHelper暴露接口获得,OrmLiteSqliteOpenHelper中默认封装了getDao(Class<T> clazz)方法和getRuntimeExceptionDao(Class<T> clazz)方法便于获得DAO对象。这两个方法本质上是一样的都是通过DAOManage获得对应的DAO对象:
/**
* Get a DAO for our class. This uses the {@link DaoManager} to cache the DAO for future gets.
*
* <p>
* NOTE: This routing does not return Dao<T, ID> because of casting issues if we are assigning it to a custom DAO.
* Grumble.
* </p>
*/
public <D extends Dao<T, ?>, T> D getDao(Class<T> clazz) throws SQLException {
// special reflection fu is now handled internally by create dao calling the database type
Dao<T, ?> dao = DaoManager.createDao(getConnectionSource(), clazz);
@SuppressWarnings("unchecked")
D castDao = (D) dao;
return castDao;
}
/**
* Get a RuntimeExceptionDao for our class. This uses the {@link DaoManager} to cache the DAO for future gets.
*
* <p>
* NOTE: This routing does not return RuntimeExceptionDao<T, ID> because of casting issues if we are assigning it to
* a custom DAO. Grumble.
* </p>
*/
public <D extends RuntimeExceptionDao<T, ?>, T> D getRuntimeExceptionDao(Class<T> clazz) {
try {
Dao<T, ?> dao = getDao(clazz);
@SuppressWarnings({ "unchecked", "rawtypes" })
D castDao = (D) new RuntimeExceptionDao(dao);
return castDao;
} catch (SQLException e) {
throw new RuntimeException("Could not create RuntimeExcepitionDao for class " + clazz, e);
}
}
他们的区别在于对处理异常的方式不一样。如RuntimeExceptionDao的类注释所述,RuntimeExceptionDao只是对异常信息进行了包装处理,并将其作为运行时异常重新抛出。
/**
* Proxy to a {@link Dao} that wraps each Exception and rethrows it as RuntimeException. You can use this if your usage
* pattern is to ignore all exceptions. That's not a pattern that I like so it's not the default.
*
* <p>
*
* <pre>
* RuntimeExceptionDao<Account, String> accountDao = RuntimeExceptionDao.createDao(connectionSource, Account.class);
* </pre>
*
* </p>
*
* @author graywatson
*/
public class RuntimeExceptionDao<T, ID> implements CloseableIterable<T> {
如果我们应用中的组件是通过继承OrmLiteBaseActivity等类的方式来使用ORMLite的话,可以使用派生出来的组件已有的getHelper()方法直接获得OrmLiteSqliteOpenHelper对象,然后调用我们在OrmLiteSqliteOpenHelper中暴露的接口获取对应的DAO对象。如下:
RuntimeExceptionDao<SimpleData, Integer> simpleDao = getHelper().getSimpleDataDao();
2. 第二种获得DAO的方式是直接通过DAOManager获得。使用这种方式需要一个ConnectionSource参数,和实体类的Class对象,ConnectionSource参数可以通过OrmLiteSqliteOpenHelper的getConnectionSource()方法获得。这两个方法定义如下。
DAOManager中的createDAO方法。
/**
* Helper method to create a DAO object without having to define a class. This checks to see if the DAO has already
* been created. If not then it is a call through to {@link BaseDaoImpl#createDao(ConnectionSource, Class)}.
*/
public synchronized static <D extends Dao<T, ?>, T> D createDao(ConnectionSource connectionSource, Class<T> clazz)
throws SQLException {
OrmLiteSqliteOpenHelper中的getConnectionSource方法。
protected AndroidConnectionSource connectionSource = new AndroidConnectionSource(this);
/**
* Get the connection source associated with the helper.
*/
public ConnectionSource getConnectionSource() {
if (!isOpen) {
// we don't throw this exception, but log it for debugging purposes
logger.warn(new IllegalStateException(), "Getting connectionSource was called after closed");
}
return connectionSource;
}
五、在Activity中使用DAO操作数据库。
1. 获得Helper对象
通过上面的分析可知,DAO可以通过Helper对象获得,也建议使用这种方式。而且使用DAO操作数据库才是我们的想要的过程,对于这一点,ORMLite本身也考虑的非常周到,他给我们提供了一些相应的快捷实现类,包括OrmLiteBaseActivity, OrmLiteBaseActivityGroup, OrmLiteBaseListActivity, OrmLiteBaseService, OrmLiteBaseTabActivity等。仔细分析其源码,发现
实现方式都是一样的,这里以OrmLiteBaseActivity为例。完整源代码如下:
/**
* Base class to use for activities in Android.
*
* You can simply call {@link #getHelper()} to get your helper class, or {@link #getConnectionSource()} to get a
* {@link ConnectionSource}.
*
* The method {@link #getHelper()} assumes you are using the default helper factory -- see {@link OpenHelperManager}. If
* not, you'll need to provide your own helper instances which will need to implement a reference counting scheme. This
* method will only be called if you use the database, and only called once for this activity's life-cycle. 'close' will
* also be called once for each call to createInstance.
*
* @author graywatson, kevingalligan
*/
public abstract class OrmLiteBaseActivity<H extends OrmLiteSqliteOpenHelper> extends Activity {
private volatile H helper;
private volatile boolean created = false;
private volatile boolean destroyed = false;
private static Logger logger = LoggerFactory.getLogger(OrmLiteBaseActivity.class);
/**
* Get a helper for this action.
*/
public H getHelper() {
if (helper == null) {
if (!created) {
throw new IllegalStateException("A call has not been made to onCreate() yet so the helper is null");
} else if (destroyed) {
throw new IllegalStateException(
"A call to onDestroy has already been made and the helper cannot be used after that point");
} else {
throw new IllegalStateException("Helper is null for some unknown reason");
}
} else {
return helper;
}
}
/**
* Get a connection source for this action.
*/
public ConnectionSource getConnectionSource() {
return getHelper().getConnectionSource();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
if (helper == null) {
helper = getHelperInternal(this);
created = true;
}
super.onCreate(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
releaseHelper(helper);
destroyed = true;
}
/**
* This is called internally by the class to populate the helper object instance. This should not be called directly
* by client code unless you know what you are doing. Use {@link #getHelper()} to get a helper instance. If you are
* managing your own helper creation, override this method to supply this activity with a helper instance.
*
* <p>
* <b> NOTE: </b> If you override this method, you most likely will need to override the
* {@link #releaseHelper(OrmLiteSqliteOpenHelper)} method as well.
* </p>
*/
protected H getHelperInternal(Context context) {
@SuppressWarnings({ "unchecked", "deprecation" })
H newHelper = (H) OpenHelperManager.getHelper(context);
logger.trace("{}: got new helper {} from OpenHelperManager", this, newHelper);
return newHelper;
}
/**
* Release the helper instance created in {@link #getHelperInternal(Context)}. You most likely will not need to call
* this directly since {@link #onDestroy()} does it for you.
*
* <p>
* <b> NOTE: </b> If you override this method, you most likely will need to override the
* {@link #getHelperInternal(Context)} method as well.
* </p>
*/
protected void releaseHelper(H helper) {
OpenHelperManager.releaseHelper();
logger.trace("{}: helper {} was released, set to null", this, helper);
this.helper = null;
}
@Override
public String toString() {
return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode());
}
}
通过源码可以知道,这些扩展类,都是内部持有了一个对应的OrmLiteSqliteOpenHelper对象。并在Activity的onCreate方法中初始化,在onDestroyed方法中销毁释放资源。
这里涉及到一个OpenHelperManager类,这个类是
OrmLiteSqliteOpenHelper的工具类,用于管理数据库连接。完整的解释如下:
/**
* This helps organize and access database connections to optimize connection sharing. There are several schemes to
* manage the database connections in an Android app, but as an app gets more complicated, there are many potential
* places where database locks can occur. This class allows database connection sharing between multiple threads in a
* single app.
*
* This gets injected or called with the {@link OrmLiteSqliteOpenHelper} class that is used to manage the database
* connection. The helper instance will be kept in a static field and only released once its internal usage count goes
* to 0.
*
* The {@link SQLiteOpenHelper} and database classes maintain one connection under the hood, and prevent locks in the
* java code. Creating multiple connections can potentially be a source of trouble. This class shares the same
* connection instance between multiple clients, which will allow multiple activities and services to run at the same
* time.
*
* Every time you use the helper, you should call {@link #getHelper(Context)} or {@link #getHelper(Context, Class)}.
* When you are done with the helper you should call {@link #releaseHelper()}.
*
* @author graywatson, kevingalligan
*/
public class OpenHelperManager {
翻译:这个类用于组织和获取数据库连接,优化连接共享。在一个app中,可能有多个模式来管理数据库连接,但是当app变得愈加复杂时,就会存在很多潜在的数据库锁发生点。这个类允许数据库连接在同一app的多个线程中共享。
这个用于注入OrmLiteSqliteOpenHelper,之后,OrmLiteSqliteOpenHelper的实例会作为一个静态属性被持有。只有当其内部的使用数变为0时才会被释放。
SQLiteOpenHelper和数据库类在这个引擎下持有一个连接,并且防止java代码中发生死锁。创建多个连接可能会存在潜在的安全问题。这个类在多个客户端中持有相同的连接实例, 并允许多个Activity和service在同一时刻运行。
每次使用这个Helper是,你应该调用getHelper(Context)或者getHelper(Context,Class),操作完成时,应该调用releaseHelper()进行释放。
处理上面的解释外,可以注意到,OrmLiteBaseActivity中的OrmLiteSqliteOpenHelper对象被修饰为volatile。这个修
饰符的作用是修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。
同时也可以想到,如果我们不想让自己的Activity继承OrmLite中的基类,大可按照其内部实现方式,将写代码迁移到我们自己的BaseActivity中。
2. 通过Helper获得的DAO对象操作数据库
到此为止,我们知道了怎么获得Helper并通过Helper获得DAO,或者RuntimeExceptionDao,下面是对数据库进行操作的基本方式。
官方的HelloAndroid中使用到了三种基本操作,插入,查询,删除。
方式如下: 插入数据的代码在Helper类的onCreate中,当然在其他地方是毫无疑问的可以的。创建对象,给对象赋值,然后直接通过DAO进行create即可(看上面
Helper类的实现)。
其次是查询,官方实例中查询和删除在HelloAndroid中进行的,都是直接通过Dao的实例调用响应的方法:
// get our dao
RuntimeExceptionDao<SimpleData, Integer> simpleDao = getHelper().getSimpleDataDao();
// query for all of the data objects in the database
List<SimpleData> list = simpleDao.queryForAll();
// our string builder for building the content-view
StringBuilder sb = new StringBuilder();
sb.append("got ").append(list.size()).append(" entries in ").append(action).append("\n");
// if we already have items in the database
int simpleC = 0;
for (SimpleData simple : list) {
sb.append("------------------------------------------\n");
sb.append("[").append(simpleC).append("] = ").append(simple).append("\n");
simpleC++;
}
sb.append("------------------------------------------\n");
for (SimpleData simple : list) {
simpleDao.delete(simple);
sb.append("deleted id ").append(simple.id).append("\n");
Log.i(LOG_TAG, "deleting simple(" + simple.id + ")");
simpleC++;
}
当然快捷操作方法还有很多,使用方式都是类似的,后面再分析。