目录导航
SQLite是轻量级的、嵌入式的、关系型数据库,目前已经在iPhone、Android等手机系统中使用,SQLite可移植性好,很容易使用,很小,高效而且可靠。
因为Android已经集成了SQLite,所以开发人员无需引入任何JAR包,而且Android也针对SQLite封装了专属的API,调用起来非常快捷方便。
我也是第一次接触SQLite,感受到它的一些不同之处,作为一门简易实用的数据库,它的学习周期其实蛮短的。对于懂关系型数据库的人来说,使用SQLite应该是得心应手的。
Android支持很多类型的存储方式,比如File文件、sharedPreference和数据库等,因为我做的应用涉及到频繁的更新操作,而且数据组成较为复杂,所以最终选择了SQLite数据库作为应用的存储方式。但是听有经验的开发人员说,在Android上使用数据文件存储极其不好,只要有ROOT权限就可以随意删除文件,所以如果你的应用属于商业性质,我推荐你做一个网络站点将数据存储于网络服务器上最好。
- 特性
不需要配置,不需要安装,也不需要管理员。SQLite不需要安装任何数据库相关的服务器,一个完整的数据库保存在磁盘上面一个文件,要想用数据库工具查看SQLite只需要将它指向那个数据库文件即可!
源代码开放, 代码95%有较好的注释。
完美支持大部分的标准SQL语句。如果你懂得MYSql、Oracle等关系型数据的使用,那么用SQLite也是得心应手。
SQLite最大的特点是,数据表中的字段是无类型的,这意味着你可以保存任何类型的数据到你所想要保存的任何表的任何列中(除了integer Primary Key)。我的理解是:一张表上的主键必须指定数据类型,而且存储更新时,主键的数据类型一定要对应;而其它的字段全部都是无类型的,你可以存字符串,也可以存int整数。
所以在建表的时候你可以这样写:
CREATE TABLE IF NOT EXISTS user_info (id INTEGER PRIMARY KEY,name,sex)除了主键外,其它字段均不用定义数据类型,即使定义了数据类型,SQLite也会忽略掉。
但是很多人还是建议带上数据类型,一来尽量让建表语句标准化便于迁移,二来让后面维护的开发人员理解每个字段到底是什么类型。
CREATE TABLE IF NOT EXISTS user_info (id INTEGER PRIMARY KEY,name VARCHAR(99),sex INTEGER )
假设主键是自增的,当insert一条数据到数据库中,获取自增主键的值可以这样写:
SELECT last_insert_rowid()
- 新增数据库
任何数据库都要先从建库和建表开始,因为我是做Android应用,所以直接通过JAVA程序来建库和建表,以后表的更新也是通过程序实现,这些功能都基于Android SQLite的SQLiteOpenHelper实现,非常地方便。
首先要了解这个核心抽象类SQLiteOpenHelper,既然是抽象类,我们就要创建一个实体类继承并实现SQLiteOpenHelper。
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper { public SQLHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); } @Override public void onCreate(SQLiteDatabase sqlitedatabase) { } @Override public void onUpgrade(SQLiteDatabase sqlitedatabase, int i, int j) { } }
构造方法四个参数:
Context指Android的上下文,Activity就继承了Context,所以在Activity中可以将对象传入构造方法中;
name指数据库名称,所以一个实体类操作一个数据库;
CursorFactory游标工厂,用于执行查询操作时控制Cursor游标对象的,传入null就表示使用系统默认游标工厂;
version当前数据库版本,这个很重要,涉及到后面的onCreate和onUpgrade方法调用,这个必须是一个整数,没有特别要求你一定必须是哪个值,我的个人建议是第一次创建时设置version为1,随后每次更新表和字段时+1。(这个下面会说怎么回事)
关于构造函数的实现,我的代码是这么做的(实例化对象时,直接调用DBHelper(Context context)这个构造函数):
private static final String DATABASE_NAME = "bless_crm.db"; private static final int DATABASE_VERSION = 1; public DBHelper(Context context) { //CursorFactory设置为null,使用默认值 super(context, DATABASE_NAME, null, DATABASE_VERSION); } public DBHelper(Context context, String name, CursorFactory factory, int version) { super(context, name, factory, version); }
要注意,并不是new DBHelper就创建了数据库,第一次创建表必须调用DBHelper的getWritableDatabase()或getReadableDatabase()方法。这个两个方法除了可以创建数据库以外,还可以更新数据库下数据表的信息,所以我建议每次打开Android应用实例化DBHelper时都调用下这个方法,这个不会对应用造成什么特殊影响。
下面是在主Activity上实例化DBHelper,启动Android应用,会首先执行这个Activity,所以在这个类里初始化SQLite数据库是最佳的。
public class MainActivity extends Activity{ DBHelper helper = null; protected void onCreate(Bundle savedInstanceState) { helper = new DBHelper(MainActivity.this); //第一次使用时创建数据库 //后面使用时更新数据表信息 helper.getWritableDatabase(); } }
注:最好是在onCreate方法中实例化DBHelper并调用getWritableDatabase()方法,如果在Activity构造函数或者其它地方调用getWritableDatabase()很可能会报错。我的理解是Context一定是在执行onCreate时才最完整,所以大家以后涉及到Context最好在onCreate中调用。
- 新增/更新数据表
完成数据库创建之后,就该做数据表的创建了。
接上面的DBHelper类,该类有两个必须实现的方法onCreate和onUpgrade,顾名思义,onCreate表示第一个数据库版本时执行的方法,onUpgrade表示随后每更新一次version版本就执行的方法。
之所以SQLiteOpenHelper构造函数中的version非常重要,就是因为它用来管理onCreate和onUpgrade的。我的理解是这样的:
当数据库创建时会设置当前数据库版本(比如我们第一次设置为version=1),这时Android就会同时执行onCreate方法,所以所有初始化表的SQL语句都得在onCreate中实现。
而假设你的Android应用有代码更新,数据库字段也需要更新,那么这时就要将更新字段的SQL写到onUpgrade中,同时要将version版本改变一下,这样SQLite就知道version有改变,去执行onUpgrade方法。
以前面SQL为例,我希望在最初版本创建一张user_info表,就可以这样写:
private static final String DATABASE_NAME = "bless_crm.db"; private static final int DATABASE_VERSION = 1; public SQLHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase sqlitedatabase) { String user_info = "CREATE TABLE IF NOT EXISTS user_info (id INTEGER PRIMARY KEY,name VARCHAR(99),sex INTEGER )"; sqlitedatabase.execSQL(user_info); } @Override public void onUpgrade(SQLiteDatabase sqlitedatabase, int i, int j) { }onCreate方法什么时候执行呢?它会在new DBHelper(context)之后,通过getWritableDatabase()或getReadableDatabase()来执行。
那么随后在编写Android应用的时候,我可能会有一些数据库表字段更新,这时就要用到onUpgrade方法并且修改version值。
private static final String DATABASE_NAME = "bless_crm.db"; private static final int DATABASE_VERSION = 2; public SQLHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase sqlitedatabase) { String user_info = "CREATE TABLE IF NOT EXISTS user_info (id INTEGER PRIMARY KEY,name VARCHAR(99),sex INTEGER,update_time varchart(99) )"; sqlitedatabase.execSQL(user_info); } @Override public void onUpgrade(SQLiteDatabase sqlitedatabase, int oldVersion, int newVersion) { if(oldVersion == 1 && newVersion == 2){ sqlitedatabase.execSQL("ALTER TABLE user_info ADD update_time varchart(99) "); } }注意上面代码三个点:
①version又原来的1变为2了,这个是我自己修改的,只要这样修改,你的Android应用重新安装到移动设备后(并启动应用),数据库版本就会更新,同时就会触发onUpgrade方法。
②注意onCreate方法,与上一段代码对比,你会发现onCreate方法中也同步更新了"update_time"字段,为什么这么做?这是为了兼容新移动设备而做的安排,如果一款手机从来没有安装过你的应用,而它第一次安装你的应该时version已经是2了,这时SQLite会把version=2当做第一个版本,只会执行onCreate方法,如果你onCreate方法不是完整的SQL,就会对功能造成影响。所以以后不论变更过什么数据库信息,都要把onCreate方法同步更新了。
③注意onUpgrade方法第二个、第三个参数,一个是旧的版本,一个是新版本值,当version改变后,Android会自动传入oldVersion和newVersion,这时就可以通过判断版本来执行对应的SQL更新操作。
这里有一个重要的逻辑场景需要考虑:
你有两部移动设备:[小米手机]和[华为手机](支持国产)。
你的Android应用中DBHelper为version=1时,两部手机都安装了你的应用,这个时候系统会知道version第一次出现,会执行onCreate方法,理论上会有一个user_info表,有字段id/name/sex。
然后你修改Android应用程序,DBHelper version=2,希望在user_info表增加一个字段update_time,这时候你只给小米手机安装了应用程序,理论上系统会检测到version变化并执行onUpgrade(xx,1,2)方法,那么小米手机的数据库有了新字段update_time,但是注意华为手机因为没安装,所以没有。
再往后,你又修改了应用程序,DBHelper version=3,希望在user_info表中增加一个字段delete_flag,这时候编写onUpgrade就要尤为注意了,你可能得这么写代码:
public void onUpgrade(SQLiteDatabase sqlitedatabase, int oldVersion, int newVersion) { if(oldVersion < 2 && newVersion >= 2){ sqlitedatabase.execSQL("ALTER TABLE user_info ADD update_time varchart(99) "); } if(oldVersion < 3 && newVersion == 3){ sqlitedatabase.execSQL("ALTER TABLE user_info ADD delete_flag integer "); } }为什么要这么写,你得考虑跨version的手机数据库同步的问题,比如华为手机,原本version为1,这时你直接给它安装version=3的应用(而跳过version=2),这时华为手机必须先执行version=2的SQL再执行version=3的SQL,这样才能保证数据库完整!
这个也是我在编写本节内容的时候突然想到的一个问题,希望引起大家的重视!
关于SQLite建库和建表就说到这里,文笔有限,表达得不是很清楚,如果想真正熟悉SQLite,我建议新手下载Android SQLite相关的教程视频来看,我一直觉得视频讲解比文档要来得直接、生动、详细!
- 增删改查
Android还提供了数据库增删改查API,要执行增删改查操作首先要调用getWritableDatabase()或getReadableDatabase()方法,这俩方法的区别是:getWritableDatabase()支持数据库"读写"操作;getReadableDatabase()支持数据库"读"操作。
增删改推荐使用getWritableDatabase()方法,而查询操作推荐用getReadableDatabase(),因为当手机存储空间被占满的时候getWritableDatabase()就无法使用了,但是getReadableDatabase()还可以继续执行查询操作。
[注:本小节代码摘自《android sqlite数据库增删改查》这个博客]
进行增删改查操作可以使用传统的SQL语句来操作:
public void savePerson(Person person) { // getWritableDatabase()如果磁盘空间满了,就只能以读的方式打开数据库,所以在磁盘空间满了的状态下调用该方法会出错 SQLiteDatabase db = dbHelper.getWritableDatabase(); db.execSQL("insert into person(name) values(?)", new Object[] { person.getName() });// 后面的数组用来填充前面的占位符 }↑增删改都调用execSQL,这里就以insert为例。
public Person findPerson(Integer personId) { SQLiteDatabase db = dbHelper.getReadableDatabase();// 如果磁盘空间没满的话,该对象与上面的对象是相等的,如果满了,该方法会调用另一只读方法,那么他们就不相等了 Cursor cursor = db.rawQuery("select * from person where id=?", new String[] { personId.toString() }); if (cursor.moveToFirst()) { Person person = new Person(); person.setId(cursor.getInt(cursor.getColumnIndex("id"))); person.setName(cursor.getString(cursor.getColumnIndex("name"))); return person; } return null; }↑查询操作通过rawQuery获取Cursor游标对象,然后通过操作游标遍历获取对应的数据,这个类似于JDBC的ResultSet。
除了使用传统的SQL来进行数据库操作外,SQLite还提供了insert/delete/update/query等自封装的增删改查操作。下面还是以新增和查询为例。
public void savePerson(Person person) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("name", person.getName()); db.insert("person", null, values);// 该方法自己构造sql语句 所以性能比execSQL // 参数2表示;如果参数3为空的话,参数2将作为insert语句的字段名插入一个null值,除了主键 其他都为空值 // 例如 db.insert("person", "name", null); // 等价于 insert into person(name) values(null); }
public Person findPerson(Integer personId) { SQLiteDatabase db = dbOpenHelp.getReadableDatabase(); /** * 1:表名 * 2:查找表列名的组合 是一个字符串数组 如果为null的话 则表示全部 * 3:查询的条件,可用占位符 * 4:是参数3中占位符的值 * 5:分组依据 * 6:分组筛选语句 * 7:排序语句 */ Cursor cursor = db.query("person", new String[] { "id", "name" }, "id=?", new String[] { personId.toString() }, null, null, null); // 上面语句等价于 Cursor cursor = // db.rawQuery("select * from person where id=?", new // String[] { personId.toString() }); if (cursor.moveToFirst()) { Person person = new Person(); person.setId(cursor.getInt(cursor.getColumnIndex("id"))); person.setName(cursor.getString(cursor.getColumnIndex("name"))); return person; } return null; }
- 组件化
Java项目做多之后,就总是会想到考虑将常用的对象单例化、将某些相似功能封装组件化,针对自己的SQLite应用,我也做了一定的封装。
①构建Javabean与数据表对应,一张表一个Javabean,Bean中所有属性与表字段对应。
②编写一个通用的增删改查操作,开发只需要传入表名和参数即可执行指定操作,减少开发工作量
③DBHelper、Manager对象通过工厂创建并保持单例
以user_info表为例,我的user_info表有如下字段:
public void onCreate(SQLiteDatabase db) { StringBuilder user_info = new StringBuilder(); user_info.append("CREATE TABLE IF NOT EXISTS \"user_info\" ("); user_info.append("\"id\" INTEGER NOT NULL,"); user_info.append("\"name\" varchar(99),"); user_info.append("\"sex\" INTEGER,"); user_info.append("\"birthday\" varchar(32),"); user_info.append("\"mobile_phone\" varchar(32),"); user_info.append("\"email\" varchar(99),"); user_info.append("\"delete_flag\" INTEGER,"); user_info.append("\"user_type\" INTEGER,"); user_info.append("\"update_time\" varchart(99),"); user_info.append("\"id_number\" varchart(99),"); user_info.append("\"qq_number\" varchart(99),"); user_info.append("\"address\" varchart(999),"); user_info.append("PRIMARY KEY (\"id\")"); user_info.append(");"); db.execSQL(user_info.toString()); }
对应的,它有一个UserInfo JavaBean:
public class UserInfo extends BasePO{ private static final long serialVersionUID = -8834697836148097731L; private Long id; private String name; private int sex; private String birthday; private String mobile_phone; private String id_number; private String qq_number; private String email; private String address; private int delete_flag; private int user_type; private String update_time; ...... }
UserInfo继承了BasePO,这个类重写了clone、toString方法,并且提供了toJSON方法方便与javascript交互,这些实用的方法都是为了方便使用。
public class BasePO implements Serializable, Cloneable { private static final long serialVersionUID = -4619334311514893448L; /** * @description : 生成UUID * @return Long */ public static Long uuid(){ return UUID.randomUUID().getMostSignificantBits(); } /** * 重写克隆方法 */ @Override protected Object clone() throws CloneNotSupportedException { try { return BeanUtils.cloneBean(this); } catch (Exception e) { Log.e(Enums.LogTagEnum.COMMON.getKey(), "克隆对象异常", e); } return null; } public String toJSON() { return ClassUtils.toJson(this); } /** * 重写toString方法 */ @Override public String toString() { return ReflectionToStringBuilder.toString(this); } }
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { ...... /** javascript与Java对象映射,页面可使用javascript:javascriptUser.xx()来调用JavascriptUser的方法 */ webView.addJavascriptInterface(new JavascriptUser(MainActivity.this), "javascriptUser"); ...... } }在Activity中定义一个与HTML页面Javascript交互的接口,到时候就可以在页面调用接口进行保存操作了。
public class JavascriptUser { protected Activity activity; protected AppContext app; protected UserManager userManager; public JavascriptUser(Activity activity) { super(); this.activity = activity; app = (AppContext) activity.getApplication(); userManager = (UserManager) BeanFactory.getDBManager(UserManager.class, activity); } public String saveUserInfo(String id_,String name,String sex,String birthday,String mobile_phone,String email,String deleteFlag_,String userType_,String id_number,String qq_number,String address){ try { boolean edit = StringUtils.isNotBlank(id_); Long id = StringUtils.isNotBlank(id_) ? Long.valueOf(id_) : UserInfo.uuid(); Integer delete_flag = StringUtils.isNotBlank(deleteFlag_) ? Integer.valueOf(deleteFlag_) : Enums.DeleteEnum.AVAILABLE.getKey(); Integer user_type = StringUtils.isNotBlank(userType_) ? Integer.valueOf(userType_) : Enums.UserTypeEnum.USER.getKey(); String update_time = DateUtil.date2string(new Date(), DateUtil.yyyy_MM_dd_HH_mm_ss); UserInfo user = new UserInfo(id, name, Integer.valueOf(sex), birthday, mobile_phone, id_number, qq_number, email, address, delete_flag, user_type, update_time); if(edit){ userManager.update(user); Log.i(Enums.LogTagEnum.COMMON.getKey(), "修改个人信息:"+user.toString()); }else{ userManager.save(user); Log.i(Enums.LogTagEnum.COMMON.getKey(), "新增个人信息:"+user.toString()); } // 初始化当前用户 app.setUser(new UserInfoBO(user)); return "true,"+user.getId(); } catch (Exception e) { Log.e(Enums.LogTagEnum.COMMON.getKey(), "输入异常:"+e.getMessage(), e); return "输入异常:"+e.getMessage(); } } ...... }
代码中userManager.update(user)用于update用户信息(一定要有主键),userManager.save(user)用于新增一条用户信息。获取UserManager对象是通过Factory来实现的,所有增删改查逻辑与UserManager有关。
public class UserManager extends BaseManagerImpl<UserInfo>{ public UserManager(Context context) { super(context); super.TABLE = "user_info"; super.clazz = UserInfo.class; } }
其实我的UserManager很简单,只有一个构造函数,告诉父类:我这个类与哪张表对应,与哪个JavaBean对应。
而执行增删改的重要操作是在BaseManagerImpl:
public class BaseManagerImpl<T> implements BaseManager<T> { protected String LOG_TAG = "BaseManagerImpl"; protected String TABLE = null; protected DBHelper dbHelper = null; protected Class clazz = null; public BaseManagerImpl(Context context) { super(); dbHelper = BeanFactory.getDBHelper(context); } @Override public void save(T t) { SQLiteDatabase db = dbHelper.getWritableDatabase(); long result = db.insert(TABLE, null, ClassUtils.getContentValues(t)); } @Override public void delete(Serializable id) { SQLiteDatabase db = dbHelper.getWritableDatabase(); int result = db.delete(TABLE, "id=?", new String[]{id.toString()}); } @Override public void update(T t) { SQLiteDatabase db = dbHelper.getWritableDatabase(); try { db.update(TABLE, ClassUtils.getContentValues(t), "id=?", new String[]{ClassUtils.getFieldValue(t, "id").toString()}); } catch (Exception e) { Log.e(LOG_TAG, "反射无法找到对象的ID值", e); } } @Override public T get(Serializable id) { String getSQL = "SELECT * FROM "+TABLE+" WHERE id=?"; List<T> list = this.findBySql(getSQL, new String[]{id.toString()}, 0, 0); if(!ObjectUtils.isEmpty(list)){ return list.get(0); } return null; } @Override public List<T> findBySql(String sql, String[] params,int pageSize,int pageNo) { if(StringUtils.isBlank(sql)){ throw new RuntimeException("findBySql的sql不能为空!"); } if(pageSize != 0 && pageNo != 0){//分页操作 int begin = (pageSize - 1)*pageNo; sql = sql + " limit "+begin + ","+pageNo; } SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.rawQuery(sql, params); List<T> tList = null; try { tList = ClassUtils.getObject(cursor, clazz); } catch (Exception e) { Log.e(LOG_TAG, "反射创建对象失败,clazz:"+clazz.getName(), e); }finally{ cursor.close(); } return tList; } @Override public void updateBySql(String sql, Object[] params) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.execSQL(sql, params); } @Override public List<T> findAll() { String findAllSql = "SELECT * FROM "+TABLE; return this.findBySql(findAllSql, null, 0, 0); } }
最后看我的Factory类
public class BeanFactory { /** * @description : 单例模式,获取DBHelper * @param context * @return DBHelper */ private static DBHelper dbHelper = null; public synchronized static DBHelper getDBHelper(Context context){ if(dbHelper == null){ dbHelper = new DBHelper(context); //执行本语句是为了自动调用建表语句 try { dbHelper.getWritableDatabase(); } catch (Exception e) { Log.e("BeanFactory", "创建表失败!", e); } } return dbHelper; } static Map<String, Object> dbMap = new HashMap<String, Object>(); @SuppressWarnings("rawtypes") public static Object getDBManager(Class clazz,Context context){ if(clazz == null){ throw new RuntimeException("class can not be null!"); } if(context == null){ throw new RuntimeException("object can not be null!"); } if(!dbMap.containsKey(clazz.getName())){//第一次创建对象 try { Constructor con = clazz.getConstructor(Context.class); Object object = con.newInstance(context); dbMap.put(clazz.getName(), object); } catch (Exception e) { Log.e(Enums.LogTagEnum.COMMON.getKey(), "对象工厂创建DBManager失败:"+clazz.getName(), e); } } return dbMap.get(clazz.getName()); } }
在将JavaBean转换为数据库数据以及反向从数据库转为JavaBean时,我用了Java反射功能。但是我要检讨----我太偷懒了,在反射解析private属性时我是直接获取Field而不是通过setMethod来实现,这显然破坏了封装的原则!
public class ClassUtils { ...... public static ContentValues getContentValues(Object object){ Class clazz = object.getClass(); Field[] fields = ClassUtils.getAllFields(object); Field.setAccessible(fields, true); if(ArrayUtils.isNotEmpty(fields)){ ContentValues cv = new ContentValues(); for (Field field : fields) { if(!"serialVersionUID".equals(field.getName())){//去掉serialVersionUID try { Object value = field.get(object); cv.put(field.getName(),value.toString()); } catch (Exception e) { Log.e(Enums.LogTagEnum.COMMON.getKey(), "", e); } } } return cv; } return null; } public static List getObject(Cursor cursor,Class clazz) throws Exception{ List list = new ArrayList(); while (cursor.moveToNext()) { Object instance = clazz.newInstance(); Field[] fields = ClassUtils.getAllFields(instance); Field.setAccessible(fields, true); for (Field field : fields) { if(!"serialVersionUID".equals(field.getName())){//去掉serialVersionUID if(field.getType().equals(Integer.class) || field.getType().getName().equals("int")){ field.set(instance, cursor.getInt(cursor.getColumnIndex(field.getName()))); }else if(field.getType().equals(String.class)){ field.set(instance, cursor.getString(cursor.getColumnIndex(field.getName()))); }else if(field.getType().equals(Long.class) || field.getType().getName().equals("long")){ field.set(instance, cursor.getLong(cursor.getColumnIndex(field.getName()))); } } } list.add(instance); } return list; } ...... }
还有关于SQLite事务可以参考这个博客:《android SQLite事务控制》,因为目前还没用到涉及事务的地方,所以我的代码中没做相关处理。
- 常见问题
1、看下面这句LIKE模糊查询查询代码,按常理来说,这样编写的代码是没问题的,但是在Sqlite下面偏偏报错。
String sql = "SELECT * FROM user_info WHERE name like '%?%'"; SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.rawQuery(sql, new String[]{"张"});编写了上面这段代码后,我在LogCat捕获到一个异常:Cannot bind argument at index 1 because the index is out of range. The statement has 0 parameters.看意思感觉是参数不对,但实际是对的,问题的根本是单引号,这里不能有单引号:SELECT * FROM user_info WHERE name like %?%。
那么我去掉单引号后还是报错:near "%": syntax error (code 1)。
结果又去网上找了下,发现在SQL中用%是不行的,必须让%以参数的形式传过去,所以正确的代码应该这样写:
String sql = "SELECT * FROM user_info WHERE name like ?"; SQLiteDatabase db = dbHelper.getReadableDatabase(); Cursor cursor = db.rawQuery(sql, new String[]{"%张%"});
以上就是我在学习Android SQLite总结的经验,用得非常肤浅,但是基本满足应用需要了。