【问题标题】:How to implement complex queries using a Content Provider?如何使用 Content Provider 实现复杂的查询?
【发布时间】:2014-08-08 18:51:23
【问题描述】:

我之所以这么问,是因为我不太清楚如何使用 Android 内容提供程序。我有一个包含 8 个表的数据库子集,我需要创建复杂的查询来获取一些数据。我的内容提供者可以很好地处理简单的查询。例如,我的 PersonModel.java 类上有一个 Person 表,我使用以下方法获取数据:

String [] projection = {PersonModel.C_FIRST_NAME, PersonModel.C_LAST_NAME};
Cursor cursor = context.getContentResolver().query(
            MyProvider.CONTENT_URI_PERSONS, projection, null,
            null, null);

而且效果很好。

在 MyProvider 上,我的每个表都有一堆 CONTENT_URI 常量。

public class MyProvider extends ContentProvider {
    MyDbHelper dbHelper;
    SQLiteDatabase db;


    private static final String AUTHORITY = "com.myapp.models";

    //Paths for each tables
    private static final String PATH_PROFILE_PICTURES = "profile_pictures";
    private static final String PATH_PERSONS = "persons";
    private static final String PATH_USERS = "users";
    ....

    //Content URIs for each table
    public static final Uri CONTENT_URI_PROFILE_PICTURES = Uri
        .parse("content://" + AUTHORITY + "/" + PATH_PROFILE_PICTURES);
    public static final Uri CONTENT_URI_PERSONS = Uri.parse("content://"
        + AUTHORITY + "/" + PATH_PERSONS);
    public static final Uri CONTENT_URI_USERS = Uri.parse("content://"
        + AUTHORITY + "/" + PATH_USERS);
    ...


    private static final int PROFILE_PICTURES = 1;
    private static final int PROFILE_PICTURE_ID = 2;
    private static final int PERSONS = 3;
    private static final int PERSON_ID = 4;
    private static final int USERS = 5;
    private static final int USER_ID = 6;

    private static final UriMatcher sURIMatcher = new UriMatcher(
        UriMatcher.NO_MATCH);
    static {
    sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES, PROFILE_PICTURES);
    sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES + "/#",
            PROFILE_PICTURE_ID);
    sURIMatcher.addURI(AUTHORITY, PATH_PERSONS, PERSONS);
    sURIMatcher.addURI(AUTHORITY, PATH_PERSONS + "/#", PERSON_ID);
    sURIMatcher.addURI(AUTHORITY, PATH_USERS, USERS);
    sURIMatcher.addURI(AUTHORITY, PATH_USERS + "/#", USER_ID);
    ...
    }



  public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {

    // Uisng SQLiteQueryBuilder instead of query() method
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

    // check if the caller has requested a column which does not exists
    //checkColumns(projection);

    int uriType = sURIMatcher.match(uri);

    switch (uriType) {
    case PROFILE_PICTURES:
        queryBuilder.setTables(ProfilePictureModel.TABLE_PROFILE_PICTURE);
        break;
    case PROFILE_PICTURE_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(ProfilePictureModel.C_ID + "="
                + uri.getLastPathSegment());
    case PERSONS:
        queryBuilder.setTables(PersonModel.TABLE_PERSON);
        break;
    case PERSON_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(PersonModel.C_ID + "="
                + uri.getLastPathSegment());
    case USERS:
        queryBuilder.setTables(UserModel.TABLE_USER);
        break;
    case USER_ID:
        // Adding the ID to the original query
        queryBuilder.appendWhere(UserModel.C_ID + "="
                + uri.getLastPathSegment());

    default:
        throw new IllegalArgumentException("Unknown URI: " + uri);

    }

    db = dbHelper.getWritableDatabase();
    Cursor cursor = queryBuilder.query(db, projection, selection,
            selectionArgs, null, null, sortOrder);
    // make sure that potential listeners are getting notified
    cursor.setNotificationUri(getContext().getContentResolver(), uri);

 }

这是我的内容提供商的一小部分。所以我的问题是:

1) 如何在内容提供程序中实现 rawQuery()?或者如何正确使用我的 queryBuilder?,假设我想使用多个表执行此查询,重命名它们并将 p1.id 作为参数传递?

SELECT p1.first_name, p1_last_name 
FROM Person p1, Person P2, Relationship r 
WHERE p1.id = ? AND 
      p1.id = r.relative_id AND
      p2.id = r.related_id;

我尝试这样做:在我的 query() 方法(如上所示)中,我有一个新案例,称为 GET_RELATIVES:

case GET_RELATIVES:
        db = dbHelper.getWritableDatabase();
        queryBuilder.setTables(PersonModel.TABLE_PERSON + " p1, "
                + PersonModel.TABLE_PERSON + " p2, "
                + RelationshipModel.TABLE_RELATIONSHIP + " r");
        queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + uri.getLastPathSegment());
        queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + "r.related_id");
        queryBuilder.appendWhere("p1."+PersonModel.C_ID + "=" + "r.relative_id");

所以我定义了一个新的 PATH,CONTENT URI 并将其添加到 UriMatcher,如下所示:

private static final String PATH_GET_RELATIVES = "get_relatives";
public static final Uri CONTENT_URI_GET_RELATIVES = Uri
        .parse("content://" + AUTHORITY + "/"
                + PATH_GET_RELATIVES);
private static final int GET_RELATIVES = 22;

private static final UriMatcher sURIMatcher = new UriMatcher(
    UriMatcher.NO_MATCH);
static {
...
sURIMatcher.addURI(AUTHORITY, PATH_GET_RELATIVES, GET_RELATIVES);
}

但这似乎不起作用,所以我想我可能在我的内容提供者或查询方法内部定义了一些错误。

2) 我不太确定为每个表设置一个名为 TABLE_ID 的常量并将其添加到 switch-case 有什么意义。那是干什么用的?我怎么称呼它?

希望任何人都可以帮助我,在此先感谢!

【问题讨论】:

    标签: java android sqlite android-contentprovider


    【解决方案1】:

    我实际上在最明显的地方找到了我的问题的答案:android 文档。

    第一个问题:实现 rawQuery。是这样的吗:

    在内容提供程序的 switch-case 中,我添加了一个新的 URI,对我来说它是表之间的 JOIN,因此我为它创建了一个新的 ContentUri 常量,一个新的 ID,并将它注册到 UriMatcher 和然后写了rawQuery。所以 MyProvider 现在看起来有点像这样:

    public class MyProvider extends ContentProvider {
    ...
    // JOIN paths
        private static final String PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = 
                "relationship_join_person_get_relatives";
    ...
    public static final Uri CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = Uri
                .parse("content://" + AUTHORITY + "/"
                        + PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES);
    ...
        private static final int RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = 21;
    private static final UriMatcher sURIMatcher = new UriMatcher(
                UriMatcher.NO_MATCH);
        static {
    ...
    //JOINS
            sURIMatcher.addURI(AUTHORITY, PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES + "/#",
                    RELATIONSHIP_JOIN_PERSON_GET_RELATIVES);
    ...
    
    public Cursor query(Uri uri, String[] projection, String selection,
                String[] selectionArgs, String sortOrder) {
    
            // Uisng SQLiteQueryBuilder instead of query() method
            SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
    
            // check if the caller has requested a column which does not exists
            //checkColumns(projection);
    
            int uriType = sURIMatcher.match(uri);
    
            switch (uriType) {
            ...
            case RELATIONSHIP_JOIN_PERSON_GET_RELATIVES:
                db = dbHelper.getWritableDatabase();
                String[] args = {String.valueOf(uri.getLastPathSegment())};
                Cursor cursor = db.rawQuery(
                        "SELECT p1.first_name, p1.last_name " +
                        "FROM Person p1, Person p2, Relationship r " +
                        "WHERE p1.id = r.relative_id AND " +
                        "p2.id = r.related_id AND " + 
                        "p2.id = ?", args);
                cursor.setNotificationUri(getContext().getContentResolver(), uri);
                return cursor;
            ...
    }
    

    为了调用 query() 方法并传递 id ad 一个参数,我在我的控制器中这样做了:

    String[] projection = { PersonModel.C_FIRST_NAME,
                    PersonModel.C_LAST_NAME };
            Cursor cursor = context.getContentResolver().query(
                    ContentUris.withAppendedId(
                            AkdemiaProvider.CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATED, id), 
                            projection, null, null, null);
    

    第二个问题:拥有 TABLE_ID 常量对于为每个表传递 id 作为参数的查询很有用,我不知道如何调用传递此类 id 的查询方法,这就是 Android 开发人员文档解释如何这样做使用 ContentUris.withAppendedId

    // Request a specific record.
    
    
    Cursor managedCursor = managedQuery(
                    ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2),
                    projection,    // Which columns to return.
                    null,          // WHERE clause.
                    null,          // WHERE clause value substitution
                    People.NAME + " ASC");   // Sort order.
    

    你们想看完整的文档去this link.

    希望这有助于其他有同样问题的人理解 ContentProvider、ContentUris 和所有这些:)

    【讨论】:

    • 你太棒了,拯救了我的一天,成就了我的一天。
    • @Carla Stabile 如果我需要传递字符串来为具有相同字符串作为字段的多个表执行联接,该怎么办。
    【解决方案2】:

    以下代码对我有用。在应用程序的内容提供者内部:

    public static final String PATH_JOIN_TWO_TABLES = "my_path";
    
        public static final Uri URI_JOIN_TWO_TABLES =
                Uri.parse("content://" + AUTHORITY + "/" + PATH_JOIN_TWO_TABLES);
    
        private static final int ID_JOIN_TWO_TABLES = 1001;
    
        private static final UriMatcher sURIMatcher = new UriMatcher(
                UriMatcher.NO_MATCH);
    
        static {
            sURIMatcher.addURI(AUTHORITY,
                    PATH_JOIN_TWO_TABLES + "/#", ID_JOIN_TWO_TABLES );
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, String[] projection, String selection,String[] selectionArgs,
                            String sortOrder, CancellationSignal cancellationSignal) {
    
            int uriType = sURIMatcher.match(uri);
                switch (uriType) {
    
                    case ID_JOIN_TWO_TABLES:
                        return getWritableDatabase()
                                .rawQuery("select * from " +
                                        "table_one" + " LEFT OUTER JOIN "
                                        + "table_two" + " ON ("
                                        + "table_one.ID"
                                        + " = " + "table_two.id" + ")", null);
                }
            return super.query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal);
        }
    

    在 Activity 或 Fragment 中进行查询时:

     Cursor cursor = getActivity().getContentResolver()
                    .query(ContentUris.withAppendedId(MYContentProvider.URI_JOIN_TWO_TABLES, MyContentProvider.ID_JOIN_TWO_TABLES), null, null, null, null);
    

    希望它对你有用。

    【讨论】:

    【解决方案3】:

    对于简单的查询,使用 ContentProvider 中的 selectionArgs。它的工作原理如下

    String[] args = { "first string", "second@string.com" };
    Cursor cursor = db.query("TABLE_NAME", null, "name=? AND email=?", args, null);
    

    在里面有 TABLE_ID 可以为每个表创建不同的查询。

    内容提供者中的所有多个表请参考以下类

    1. Vogella Tutorial 1
    2. Vogella Tutorial 2
    3. Best practices for exposing multiple tables using content providers in Android

    【讨论】:

    • 感谢您的回答。实际上,我做了 Vogella 教程来制作我的内容提供者,但是如果您发现那里没有复杂的查询,那是我的主要问题。你知道怎么做吗?
    • 它包含大部分查询所需的代码。复杂查询是您的业务逻辑。为此,请查找我上面提到的 selectionArgs 的示例或查找 SqliteQueryBuilder 的示例。此链接应该对programcreek.com/java-api-examples/… 有所帮助。如果有帮助,请采纳答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-03
    • 1970-01-01
    • 1970-01-01
    • 2019-07-01
    相关资源
    最近更新 更多