[lottery] 05_Android数据库通用操作

Android 4.0

数据库操作

1、使用反射
2、使用注解
数据库框架
数据库操作
数据库通用操作
对于主键,_id integer primary key autoincrement
主键可以有int,String,double等,用Serialiable通用
一、操作数据库实例
①写一个类NewsDBHelper继承SQLiteOpenHelper 类,在里面创建数据库 
package cn.zengfansheng.db.dao;
 
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
 
public class NewsDBHelper extends SQLiteOpenHelper {
 
    private final static String DBNAME = "news.DB";
    private final static int VERSION = 1;
 
    public static final String TABLE_NEWS_NAME = "news";// 新闻表名
 
    public static final String TABLE_NEWS_ID = "_id";// 通用的主键
    public static final String TABLE_NEWS_TITLE = "title";// 新闻标题
    public static final String TABLE_NEWS_SUMMARY = "summary";// 新闻摘要
 
    public NewsDBHelper(Context context) {
        super(context, DBNAME, null, VERSION);
    }
 
    @Override
    public void onCreate(SQLiteDatabase db) {
 
        // 创建数据库
        String sql = "create table " + TABLE_NEWS_NAME + "(" + TABLE_NEWS_ID
                + " integer primary key autoincrement, " + TABLE_NEWS_TITLE
                + " varchar(50), " + TABLE_NEWS_SUMMARY + " varchar(200)" + ")";
        db.execSQL(sql);
    }
 
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 数据库版本发生变化时,会调用此方法
    }
}
②写一个通用的数据库的CRUD操作的NewsDao  
package cn.zengfansheng.db.dao;
import java.util.List;
import cn.zengfansheng.db.domain.News;
public interface NewsDao {
    /**
     * 增
     * @param news
     * @return
     */
    public abstract long insert(News news);
    /**
     * 删
     * @param _id
     * @return
     */
    public abstract int delete(int _id);
    /**
     * 改
     * @param news
     * @return
     */
    public abstract int update(News news);
    /**
     * 查
     * @return
     */
    public abstract List<News> findAll();
}
③写一个NewDao的实现类NewsDaoImpl类,具体实现对数据库的CRUD
package cn.zengfansheng.db.dao.impl;
 
import java.util.ArrayList;
import java.util.List;
 
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import cn.zengfansheng.db.dao.NewsDBHelper;
import cn.zengfansheng.db.dao.NewsDao;
import cn.zengfansheng.db.domain.News;
 
public class NewsDaoImpl implements NewsDao {
 
    private NewsDBHelper dbHelper;
    private SQLiteDatabase database;
 
    private static final String TABLE_NEWS_NAME = NewsDBHelper.TABLE_NEWS_NAME;// 新闻表名
 
    private static final String TABLE_ID = NewsDBHelper.TABLE_NEWS_ID;// 通用的新闻主键
    private static final String TABLE_NEWS_TITLE = NewsDBHelper.TABLE_NEWS_TITLE;// 新闻标题
    private static final String TABLE_NEWS_SUMMARY = NewsDBHelper.TABLE_NEWS_SUMMARY;// 新闻摘要
 
    public NewsDaoImpl(Context context) {
        this.dbHelper = new NewsDBHelper(context);
        database = dbHelper.getWritableDatabase();
    }
 
    @Override
    public long insert(News news) {// 主键不需要插入
 
        //SQLiteDatabase database = dbHelper.getWritableDatabase();
 
        ContentValues values = new ContentValues();
        values.put(TABLE_NEWS_TITLE, news.getTitle());
        values.put(TABLE_NEWS_SUMMARY, news.getSummary());
 
        long insert = database.insert(TABLE_NEWS_NAME, null, values);
 
        database.close();
        return insert;
    }
 
    @Override
    public int delete(int _id) {// 系统默认主键_id从1开始增长,注意whereClause=?
 
        //SQLiteDatabase database = dbHelper.getWritableDatabase();
        int delete = database.delete(TABLE_NEWS_NAME, TABLE_ID+"=?", new String[]{String.valueOf(_id)});
 
        database.close();
        return delete;
    }
 
    @Override
    public int update(News news) {
 
        //SQLiteDatabase database = dbHelper.getWritableDatabase();
 
        ContentValues values = new ContentValues();
        values.put(TABLE_NEWS_TITLE, news.getTitle());
        values.put(TABLE_NEWS_SUMMARY, news.getSummary());
 
        int update = database.update(TABLE_NEWS_NAME, values,TABLE_ID+"=?",new String[]{String.valueOf(news.get_id())});
 
        database.close();
        return update;
        // update table news set title=?,summary=? where _id = ?;
    }
 
    @Override
    public List<News> findAll() {
 
        //SQLiteDatabase database = dbHelper.getWritableDatabase();
 
        Cursor cursor = database.query(TABLE_NEWS_NAME, null, null, null, null, null, null);
        List<News> list = new ArrayList<News>();
        if (cursor!=null) {
 
            while (cursor.moveToNext()) {
                News news = new News();
                // int _id = cursor.getInt(cursor.getColumnIndex(TABLE_ID));
                String title = cursor.getString(cursor.getColumnIndex(TABLE_NEWS_TITLE));
                String summary = cursor.getString(cursor.getColumnIndex(TABLE_NEWS_SUMMARY));
                // news.set_id(_id);
                news.setTitle(title);
                news.setSummary(summary);
                list.add(news);
            }
        }
 
        cursor.close();
        database.close();
        return list;
    }
}
④测试类DBTest.java
package cn.zengfansheng.db.test;
import java.util.List;
import android.database.sqlite.SQLiteDatabase;
import android.test.AndroidTestCase;
import cn.zengfansheng.db.dao.NewsDBHelper;
import cn.zengfansheng.db.dao.NewsDao;
import cn.zengfansheng.db.dao.impl.NewsDaoImpl;
import cn.zengfansheng.db.domain.News;

public class DBTest extends AndroidTestCase {
    public void testDBhelp() {
        NewsDBHelper dbHelper = new NewsDBHelper(getContext());
        SQLiteDatabase database = dbHelper.getWritableDatabase();
    }
    public void testInsert() {
        NewsDao newsDao = new NewsDaoImpl(getContext());
        News news = new News();
        for (int i = 0; i < 20; i++) {
            news.setTitle("news_title_" + i);
            news.setSummary("news_summary_" + i);
            newsDao.insert(news);
        }
    }
    public void testDelete() {
        NewsDao newsDao = new NewsDaoImpl(getContext());
        int _id = 1;
        int delete = newsDao.delete(_id);
        System.out.println("delete:" + delete);
    }
    public void testUpdate() {
        NewsDao newsDao = new NewsDaoImpl(getContext());
        News news = new News();
        news.set_id(19);
        news.setTitle("hacket title");
        news.setSummary("hacket summary");
        int update = newsDao.update(news);
        System.out.println("update:" + update);
    }
    public void testFindAll() {
        NewsDao newsDao = new NewsDaoImpl(getContext());
        List<News> newss = newsDao.findAll();
        for (News news : newss) {
            System.out.println(news.getTitle() + ":" + news.getSummary());
        }
    }
}
二、通用的数据库操作 
1、抽取出来一个通用的Dao.java

package cn.zengfansheng.db.dao;
import java.io.Serializable;
import java.util.List;
public interface Dao<M> {
    // note*********************1、需要解决的问题*********************
    // 1、insert操作
    // ①要操作的表名
    // ②要操作的实体属性和数据库表中列名的对应关系
    // 2、delete操作
    // ①要操作的表名
    // ②主键数据的获取
    // 3、update操作
    // ①要操作的表名
    // ②要操作的实体属性和数据库表中列名的对应关系
    // ③主键
    // 4、query操作
    // ①要操作的表名
    // ②实体实例的创建
    // ③要操作的数据库中表的列名和实体属性的对应关系
    
    // note*********************2、具体实现*********************
    /**
     * 增
     * @param m 要操作的实体
     * @return  the row ID of the newly inserted row, or -1 if an error occurred
     */
    public abstract long insert(M m);
    
    /**
     * 删
     * @param _id 根据_id来删除
     * @return  受影响的行数
     */
    public abstract int delete(Serializable _id);
    
    
    /**
     * 改
     * 
     * @param m
     *            要修改成的实体
     * @return 受影响的行数
     */
    public abstract int update(M m);
    /**
     * 查(所有)
     * 
     * @return 所有结果的实体集
     */
    public abstract List<M> findAll();
}
 
2、需要解决的问题
// 问题一:表名的获取
// 问题二:数据库表中列的内容如何设置到实体中
// 问题三:实体属性与数据库表列名的对应关系
// 问题四:主键数据获取
// 问题五:解决实例的创建
问题一、获取要操作的表名
①在操作的实体上它知道要操作的表名
②在要操作的实体类上添加一个注解Annotation(注意@Retention和@Target)
@TableName(value = DBHelper.TABLE_NEWS_NAME//数据库中对应的表名
public class News {}
③注解类:用来表示操作的实体和表名对应的关系
package cn.zengfansheng.db.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 注解类
 * 要操作的实体与表名对应的关系
 * @author hacket
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TableName {
    String value();
}
④问题一解决:获取要操作的表名
// ===========================问题一、获取要操作的表名===========================
// 如何确定要操作的表名?
// ①在操作的实体上它知道要操作的表名
// ②在要操作的实体上添加一个注解annotation(注意@Retention和@Target)
/**
 * 获取表名
 * @return
 */
private String getTableName() {
    // 1、获取M的实例
    M m = getInstance();
    //2、通过实例来获取注解
    TableName tableNameInstance = m.getClass().getAnnotation(TableName.class);
    if (tableNameInstance != null) {
        String tableName = tableNameInstance.value();
        return tableName;
    }
    return null;
}
问题二:数据库表中列的内容如何设置到实体中
①在实体属性上增加属性注解
/**
 * 新闻表domain
 * @author hacket
 */
@TableName(value = DBHelper.TABLE_NEWS_NAME//数据库中对应的表名
public class News {

    @ColumnName(value=DBHelper.TABLE_ID)   //数据库中对应的列名
    private int _id;
    @ColumnName(value = DBHelper.TABLE_NEWS_TITLE)
    private String title;
    @ColumnName(value = DBHelper.TABLE_NEWS_SUMMARY)
    private String summary;
}
②注解类ColumnName.java:用来标识实体的属性和数据库中列名的对应关系
package cn.zengfansheng.db.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * 要操作的数据库中表的列名和实体属性的对应关系
 * 
 * @author hacket
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ColumnName {
    String value();
}
③通过获取字段上面的注解,来获取实体对应属性在数据库表中对应哪一列
④然后设置给该列对应的值
// NOTES=============问题二、要操作的数据库中表的列名和实体属性的对应关系=============
// 如何确定数据库中列名和实体属性的对应关系?
// ①在实体属性上增加属性注解
// ②通过获取字段上面的注解,来获取实体对应属性在数据库表中对应哪一列
// ③然后设置给该列对应的值
/**
 * 填充实体属性的值(将数据库中的值设置给实体的属性)
 * 
 * @param cursor
 * @param m
 */
private void fillFields(Cursor cursor, M m) {
    // 1、获取实例m上所有的字段(包括private)
    Field[] declaredFields = m.getClass().getDeclaredFields();
    // 2、遍历所有的字段,为每个字段设置值(从数据库中查询出来的值)
    for (Field field : declaredFields) {
        // 2-1、设置该field可访问,暴力反射
        field.setAccessible(true);
        // 2-2、获取字段field上的注解
        ColumnName columnNameAnnotation = field
                .getAnnotation(ColumnName.class);
        if (columnNameAnnotation != null) {// 判断该字段上有注解(因为有一些字段没有注解的)
            // 2-3、获取该注解的值(也就是数据库中对应的列名)
            String columnName = columnNameAnnotation.value();
            // 2-4、通过游标获取该列的值
            String columnValue = cursor.getString(cursor
                    .getColumnIndex(columnName));
            try {
                // 2-5、设置该列的值给对应对应的对应的属性
                // int modifiers = field.getModifiers();获取字段的修饰符
                // 2-6、获取字段的类型
                Class<?> type = field.getType();
                String typeName = type.getSimpleName();
                if (typeName.equals("String")) {
                    field.set(m, columnValue);
                } else if (typeName.equals("float")) {
                    field.setFloat(m, Float.parseFloat(columnValue));
                } else if (typeName.equals("double")) {
                    field.setDouble(m, Double.parseDouble(columnValue));
                } else if (typeName.equals("long")) {
                    field.setLong(m, Long.parseLong(columnValue));
                } else if (typeName.equals("int")) {
                    field.setInt(m, Integer.parseInt(columnValue));
                } else if (typeName.equals("short")) {
                    field.setShort(m, Short.parseShort(columnValue));
                } else if (typeName.equals("byte")) {
                    field.setByte(m, Byte.parseByte(columnValue));
                } else if (typeName.equals("char")) {
                    field.setChar(m, columnValue.toCharArray()[0]);// 注意这里的char和String的转换
                } else if (typeName.equals("boolean")) {
                    field.setBoolean(m, Boolean.parseBoolean(columnValue));
            }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }
}
问题三:实体的属性与数据库表中列名的对应关系 
// NOTES===========================问题三、实体的属性与数据库表中列名的对应关系===========================
/**
 * 设置数据到ContentValues(将实体上的数据设置到ContentValues中)
 * @param m 实体M
 * @param values 要将数据设置给哪一个ContentValues
 */
private void fillValues(M m, ContentValues values) {
    // 在问题二中已经通过注解将实体的属性与数据库表中列名的关系给解决
    // 1、获取M实体上所有的属性
    Field[] declaredFields = m.getClass().getDeclaredFields();
    // 2、循环遍历
    for (Field field : declaredFields) {
        field.setAccessible(true);// 暴力破解
        // 获取字段field上的注解,并获取该字段上注解对应的值(也就是该字段在数据库中对应的列名)
        ColumnName columnAnnotation = field.getAnnotation(ColumnName.class);
        
        if (columnAnnotation != null) {
            
            // 判断是否是主键ID标识的注解
            ID idAnnotation = field.getAnnotation(ID.class);
            String key = columnAnnotation.value();
            if (idAnnotation != null) {// ID注解不为null
                if (idAnnotation.autoincrement() == true) {// ID注解主键是否为自动增长
                    // 有ID主键且自动增长为true,那么即是主键
                    // 那么就不能设置id的值,否则系统不能管理主键的自动增长,id就会从0开始
                    continue;
                } else {// 不是自动增长
                }
            } else {// 没有ID注解, 说明不是主键
            }
            String value = null;
            try {
                // 获取该字段的值,并设置给ContentValues
                value = String.valueOf(field.get(m));// FIXME:小心ClassCastException异常
                values.put(key, value);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }
}
问题四:主键_id数据获取
①定义一个ID注解类
package cn.zengfansheng.db.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 主键ID
 * @author hacket
 */

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {

    /**
     * 是否可以自动增长,true为可以,false为不可以
     * @return
     */

    boolean autoincrement();
}
②在实体类的属性上具有主键的属性上增加ID注解
@ID(autoincrement = true)
@ColumnName(StudentDBHelper.TABLE_ID)
private int _id;
③通过字节码获取字段,判断字段上是否有主键ID, 如果有那么就是主键,如果没有,那么就不是主键
// NOTES===========================问题四:主键_id数据获取===========================
// 在实体上用一个
private String getId(M m) {
    // 1、获取M实体上所有的属性
    Field[] declaredFields = m.getClass().getDeclaredFields();
    // 2、循环遍历
    for (Field field : declaredFields) {
        field.setAccessible(true);// 暴力破解
        // 获取具有ID注解的字段
        ID idAnotation = field.getAnnotation(ID.class);
        // 如果不为null,即具有ID注解,说明该字段是主键
        if (idAnotation != null) {
            try {
                return String.valueOf(field.get(m));// FIXME:小心ClassCast异常
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            }
        }
    }
    return null;
}
问题五(核心)、实体实例的创建(难)
通过DaoSupport来获取当前正在运行的实例
1、this.getClass(),获取的是当前正在运行的class,而.calss不能获取当前正在运行的class
2、所有泛型的类(如:DaoSupport<M>)都实现了ParameterizedType接口
步骤:
①获取当前正在运行的class,StudentDaoImpl
②然后获取StudentDaoImpl类的父类DaoSupport<M>
③判断父类是否带有泛型,如果有,那么转为ParameterizedType类型
④通过getActualTypeArguments来获取泛型上的类
⑤获取第一个,创建实例clazz.newInstance();
// ===========================问题五、实体实例的创建(难)====================
/**
 * 获取一个实例
 * @return
 */
@SuppressWarnings("unchecked")
private M getInstance() {
    
    // 1、获取DaoSupport的子类StudentDaoImpl的父类带有的泛型类DaoSupport<Student>
    Type daoSupportClass = this.getClass().getGenericSuperclass();
    // 2、判空和判断是否是带有泛型的类
    if (daoSupportClass!=null && daoSupportClass instanceof ParameterizedType) {
        
        // 3、 将DaoSupport转换为ParameterizedType
        ParameterizedType parameterizedType = (ParameterizedType) daoSupportClass;
        
        // 4、获取DaoSupport所有的泛型上的类
        Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
        
        Class<?> clazz = (Class<?>) actualTypeArguments[0];
         
        try {
            // 5、创建实例
            return (M) clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    return null;
}
注意:对于主键ID,不能为其设置数据,否则会出现_id=0的情况,
系统管理的话会从1开始