安卓学习日志 Day16 — 在应用中使用SQLite_HEY-BLOOD的博客-程序员宅基地

技术标签: 安卓学习日志  

概述

SQLite 数据库已经了解得差不多了。

下面将侧重与如何在应用中创建数据库,然后学习如何插入及查询数据。

起始项目

Pets 应用 的初始代码可以从 GitHub 仓库获得,使用 Git 命令克隆到初始代码:

git clone -b starting-point https://github.com/HEY-BLOOD/Pets.git

之后导入 到 Android Studio 中运行,。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-emA6V6f3-1612345581149)(.\Day16~2021-02-02.assets\image-20210203000629731.png)]

定义架构和协定

SQL 数据库的主要原则之一是架构,即数据库组织方式的正式声明。架构反映在您用于创建数据库的 SQL 语句中。您可能会发现创建伴随类(称为协定类)很有用,该类以系统化、自记录的方式明确指定了架构的布局。

协定类是定义 URI、表和列名称的常量的容器。通过协定类,您可以在同一软件包的所有其他类中使用相同的常量。这样一来,您就可以在一个位置更改列名称并将其传播到整个代码中。

组织协定类的一种良好方法是将对整个数据库而言具有全局性的定义放入类的根级别。然后,为每个表创建一个内部类。每个内部类都枚举相应表的列。

Schema

架构就是规划好的 数据库基本结构,如 表名、字段名、字段类型等。

比如 Pets 应用的数据库架构如下(只含一个 pets 数据表):

pets
_id(INTEGER) name(TEXT) breed(TEXT) gender(INTEGER) weight(INTEGER)
…… …… …… …… ……

创建 Contract 类

因为将要用到大量与数据相关的类,所以创建一个名叫 data 的 Java 包,这样可以将具有特定数据功能的类与 Activity 类区分开来。

然后创建 PetContract 类,作为数据库的协定。因为这个类是用于提供常量构建数据库的,所以加上 final 关键字而不能被扩展,并定义一个 私有且无参数的构造函数,使得不能在外部创建实例。

再定义表名 和字段名,PetContract.java

package com.example.pets.data;

import android.provider.BaseColumns;

public final class PetContract {
    

    private PetContract() {
    
    }

    public static final class PetEntry implements BaseColumns {
    

        public final static String TABLE_NAME = "pets";

        public final static String _ID = BaseColumns._ID;

        public final static String COLUMN_PET_NAME = "name";

        public final static String COLUMN_PET_BREED = "breed";

        public final static String COLUMN_PET_GENDER = "gender";

        public final static String COLUMN_PET_WEIGHT = "weight";

        /**
         * Possible values for the gender of the pet.
         */
        public static final int GENDER_UNKNOWN = 0;
        public static final int GENDER_MALE = 1;
        public static final int GENDER_FEMALE = 2;
    }
}

当数据库的所需的所有常量都定义完成后,在需要用到的地方更新代码,比如在 EditorActivtymGender 性别变量在赋值时应使用 PetEntry 中预定的常量,如:

mGender = PetEntry.GENDER_MALE; // Male

别忘了先导入这个内部类:

import com.example.pets.data.PetContract.PetEntry;

更改详细的代码更改,参考 此链接

使用 SQLiteOpenHelper 创建数据库

定义了数据库架构的协定后,应实现用于创建和维护数据库和表的SQLiteOpenHelper 抽象类 。

Android 会将您的数据库存储在您应用的私有文件夹中。您的数据安全无虞,因为在默认情况下,其他应用或用户无法访问此区域。

SQLiteOpenHelper 类包含一组用于管理数据库的实用 API。当您使用此类获取对数据库的引用时,系统仅在需要时才执行可能需要长时间运行的数据库创建和更新操作,而不是在应用启动期间执行。您仅需调用 getWritableDatabase()getReadableDatabase() 即可。

如需使用 SQLiteOpenHelper,请创建一个用于替换 onCreate()onUpgrade() 回调方法的子类。您可能还需要实现 onDowngrade()onOpen() 方法,但这些方法并非必需。

继承 SQLiteOpenHelper

这个实现类将具有以下功能:

  1. 在首次访问应用使创建要使用的数据库
  2. 下次在设备上访问应用时,读取现有的数据库
  3. 在数据库版本发送更改时,更新数据库的架构

为此需要以下步骤:

  • 创建一个类 ,继承自 SQLiteOpenHelper
  • 分别为数据库的名称和 数据库版本定义常量
  • 提供一个构造函数
  • 实现 onCreate() 方法,这会在首次创建数据库时使用
  • 实现 onUpgrade() 方法,在数据库架构出现变化时使用

最终 SQLiteOpenHelper 的实现类 PetDbHelper 如下,在 data Java 包中创建:

package com.example.pets.data;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import androidx.annotation.Nullable;

import com.example.pets.data.PetContract.PetEntry;

public class PetDbHelper extends SQLiteOpenHelper {
    
    public static final String LOG_TAG = PetDbHelper.class.getSimpleName();

    /**
     * Name of the database file
     */
    private static final String DATABASE_NAME = "shelter.db";

    /**
     * Database version. If you change the database schema, you must increment the database version.
     */
    private static final int DATABASE_VERSION = 1;

    /**
     * Constructs a new instance of {@link PetDbHelper}.
     *
     * @param context of the app
     */
    public PetDbHelper(@Nullable Context context) {
    
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    
        // Create a String that contains the SQL statement to create the pets table
        String SQL_CREATE_PETS_TABLE = "CREATE TABLE " + PetEntry.TABLE_NAME + " ("
                + PetEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
                + PetEntry.COLUMN_PET_NAME + " TEXT NOT NULL, "
                + PetEntry.COLUMN_PET_BREED + " TEXT, "
                + PetEntry.COLUMN_PET_GENDER + " INTEGER NOT NULL, "
                + PetEntry.COLUMN_PET_WEIGHT + " INTEGER NOT NULL DEFAULT 0);";
        // Execute the SQL statement
        db.execSQL(SQL_CREATE_PETS_TABLE);
    }

    /**
     * This is called when the database needs to be upgraded.
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    
        // The database is still at version 1, so there's nothing to do be done here.
    }
}

数据库名称定义为 shelter.db,版本 1,一个调用了父类方法的构造函数。

onCreate 方法中 定义创建数据库的 SQL语句,并 调用数据库对象 的 execSQL 创建数据库。

由于目前数据库架构为首次创建,无须更改,所以 onUpgrade 方法留空处理。

不过这些都还只是定义,目前并没有使用到这个扩展类。

创建并连接数据库

现在用于 创建和 管理数据库的 PetDbHelper 类已经准备好了,如何使用它来创建数据库?

可以在 CatalogActivity 中定义一个 displayDatabaseInfo 辅助方法,用来执行 创建数据库 并 访问数据库 的操作:

    /**
     * Temporary helper method to display information in the onscreen TextView about the state of
     * the pets database.
     */
    private void displayDatabaseInfo() {
    
        // To access our database, we instantiate our subclass of SQLiteOpenHelper
        // and pass the context, which is the current activity.
        PetDbHelper mDbHelper = new PetDbHelper(this);

        // Create and/or open a database to read from it
        SQLiteDatabase db = mDbHelper.getReadableDatabase();

        // Perform this raw SQL query "SELECT * FROM pets"
        // to get a Cursor that contains all rows from the pets table.
        Cursor cursor = db.rawQuery("SELECT * FROM " + PetEntry.TABLE_NAME, null);
        try {
    
            // Display the number of rows in the Cursor (which reflects the number of rows in the
            // pets table in the database).
            TextView displayView = (TextView) findViewById(R.id.text_view_pet);
            displayView.setText("Number of rows in pets database table: " + cursor.getCount());
        } finally {
    
            // Always close the cursor when you're done reading from it. This releases all its
            // resources and makes it invalid.
            cursor.close();
        }
    }
  • 8 行,实例化 PetDbHelper 对象(继承自 SQLiteOpenHelper 抽象类)
  • 11 行,创建数据库 或 打开已经存在的数据库,getReadableDatabase() 只能获取只读的数据库对象,如要 执行 插入、删除等操作需使用 getWritableDatabase() 方法
  • 15 行,从数据表中查询所有记录,保存在 Cursor 对象中
  • 16 ~ 25 行,将 数据表的行号 显示 在 界面当中,并始终关闭 数据库的游标对象

不要忘了在 CatalogActivtyonCreate 方法中 调用 辅助方法:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_catalog);

		…………

        displayDatabaseInfo();
    }

最后运行应用,如无误应该能看到如下的一行文本,提示数据库中 有 0 条数据,因为这是首次创建的数据库,未插入任何数据:

代码更改前后 差异对比

将信息添加到数据库

通过将 ContentValues 对象传递给 insert() 方法,将数据插入到数据库中:

// Gets the data repository in write mode
SQLiteDatabase db = dbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle);

// Insert the new row, returning the primary key value of the new row
long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

insert() 的第一个参数就只是表名。

第二个参数将指示框架在 ContentValues 为空(即您没有 put 任何值)时应执行哪些操作。如果您指定列名称,框架会插入一行,并将该列的值设置为 null。如果您指定 null(如此代码示例所示),框架在没有值时不会插入行。

insert() 方法会返回新创建行的 ID;如果在插入数据时出错,会返回 -1。如果您的数据与数据库中已有的数据之间存在冲突,就会出现这种情况。

通过菜单项插入虚假宠物

CatalogActivity 的菜单项中 有两个选项,分别 为 Insert Dummy DataDelete All Pets 。当我们点击 Insert Dummy Data 时将直接添加一只宠物到数据库中。

所以在 CatalogActivity.java 中定义一个插入虚拟数据的辅助方法 insertPet()

    /**
     * Helper method to insert hardcoded pet data into the database. For debugging purposes only.
     */
    private void insertPet() {
    
        // Gets the database in write mode
        SQLiteDatabase db = mDbHelper.getWritableDatabase();

        // Create a ContentValues object where column names are the keys,
        // and Toto's pet attributes are the values.
        ContentValues values = new ContentValues();
        values.put(PetEntry.COLUMN_PET_NAME, "Toto");
        values.put(PetEntry.COLUMN_PET_BREED, "Terrier");
        values.put(PetEntry.COLUMN_PET_GENDER, PetEntry.GENDER_MALE);
        values.put(PetEntry.COLUMN_PET_WEIGHT, 7);

        // Insert a new row for Toto in the database, returning the ID of that new row.
        // The first argument for db.insert() is the pets table name.
        // The second argument provides the name of a column in which the framework
        // can insert NULL in the event that the ContentValues is empty (if
        // this is set to "null", then the framework will not insert a row when
        // there are no values).
        // The third argument is the ContentValues object containing the info for Toto.
        long newRowId = db.insert(PetEntry.TABLE_NAME, null, values);
    }

接着 在 点击 nsert Dummy Data 菜单项时 的回调中 , 调用 插入虚拟宠物的方法 ,并更新 显示的记录条数(CatalogActivity 中)

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    
        // User clicked on a menu option in the app bar overflow menu
        switch (item.getItemId()) {
    
            // Respond to a click on the "Insert dummy data" menu option
            case R.id.action_insert_dummy_data:
                insertPet();
                displayDatabaseInfo();
                return true;
            // Respond to a click on the "Delete all entries" menu option
            case R.id.action_delete_all_entries:
                // Do nothing for now
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

程序运行如下 每插入一次虚拟数据,数据库中的行数 就会 加 1代码更改前后 差异对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KqHhzY8s-1612345581152)(.\Day16~2021-02-02.assets\Video_20210203_032957_590.gif)]

通过编辑器插入宠物

想要通过编辑器插入宠物 ,首先要获得 编辑器中 输入的内容,然后将其 封装成 ContentValues 对象 并写入到数据库中。

而 这一系列 的操作都应该是在编辑完宠物信息后,点击提交时触发:

因此可以采取和 插入虚拟时采取同样的方法,当点击提交时,通过调用一个辅助方法 来 插入宠物信息,并在插入完成后 退出到父级 Activity,在 EditorActivity 中 更改:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    
        // User clicked on a menu option in the app bar overflow menu
        switch (item.getItemId()) {
    
            // Respond to a click on the "Save" menu option
            case R.id.action_save:
                // Save pet to database
                insertPet();
                // Exit activity, back to parent Activity
                finish();
                return true;
            ………………
        }
        return super.onOptionsItemSelected(item);
    }

然后 在 EditorActivity 中 实现,用于插入宠物的 insertPet 辅助方法:

    /**
     * Get user input from editor and save new pet into database.
     */
    private void insertPet() {
    
        // Read from input fields
        // Use trim to eliminate leading or trailing white space
        String nameString = mNameEditText.getText().toString().trim();
        String breedString = mBreedEditText.getText().toString().trim();
        String weightString = mWeightEditText.getText().toString().trim();
        int weight = Integer.parseInt(weightString);

        // Create database helper
        PetDbHelper mDbHelper = new PetDbHelper(this);

        // Gets the database in write mode
        SQLiteDatabase db = mDbHelper.getWritableDatabase();

        // Create a ContentValues object where column names are the keys,
        // and pet attributes from the editor are the values.
        ContentValues values = new ContentValues();
        values.put(PetEntry.COLUMN_PET_NAME, nameString);
        values.put(PetEntry.COLUMN_PET_BREED, breedString);
        values.put(PetEntry.COLUMN_PET_GENDER, mGender);
        values.put(PetEntry.COLUMN_PET_WEIGHT, weight);

        // Insert a new row for pet in the database, returning the ID of that new row.
        long newRowId = db.insert(PetEntry.TABLE_NAME, null, values);

        // Show a toast message depending on whether or not the insertion was successful
        if (newRowId == -1) {
    
            // If the row ID is -1, then there was an error with insertion.
            Toast.makeText(this, "Error with saving pet", Toast.LENGTH_SHORT).show();
        } else {
    
            // Otherwise, the insertion was successful and we can display a toast with the row ID.
            Toast.makeText(this, "Pet saved with row id: " + newRowId, Toast.LENGTH_SHORT).show();
        }
    }

insertPet 辅助 方法 只获取了 宠物的 姓名、品种和体重,而并没有获取 性别,可在 封装 ContentValues 对象时却 直接使用了 成员变量中 mGender

这是因为在 编辑器的性别 Spinner 创建就已经绑定了 监听器,每当 选择不同的性别时就会实时获取到,所以在 insertPet 方法中就不需要再去获取性别。

然后需要更改 CatalogActivity 用于显示 数据库 记录的 displayDatabaseInfo() 方法的调用位置,因为当我们进入编辑器时,CatalogActivity 并没有被销毁,而是处于暂停的状态,而当我们在编辑器中编辑完数据并保存后,会直接退回到 CatalogActivity,这时候原本的 CatalogActivity 应该是从停止的状态返回到启动的状态,而不会调用 onCreate() 方法,而是调用 onStart()方法,所以将显示数据库记录的 displayDatabaseInfo() 辅助方法移到 onStart()方法中。

@Override
protected void onStart() {
    
    super.onStart();
    displayDatabaseInfo();
}

这样当我们从编辑器中保存数据后,再回到 CatalogActivity,就能够立即看到插入数据后的总记录数。代码更改前后 差异对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ifcKlCeB-1612345581155)(.\Day16~2021-02-02.assets\Video_20210203_035809_872.gif)]

数据库查询方法

选择 宠物信息已经成功添加到数据库当中了,问题是 如何 将数据库中的宠物信息 显示出来呢,这就需要 对数据库进行读取。

读取数据库最直接的方法是 调用 execSQL()rawQuery() 方法来 执行 SQL 查询语句,但这个方法不安全,有 SQL 注入的风险存在,同时也在 组织查询语句也 容易出现错误,因此不推荐。

更好的 方法是 使用 query() 方法来查询数据,并充分利用 数据库协定中定义的常量,以此避免 更多的 错误情况。

如需从数据库中读取信息,请使用 query() 方法,向其传递您的选择条件和所需的列。该方法合并了 insert()update() 元素,不过列列表定义了要提取的数据(“预测值”),而不是要插入的数据。查询结果会包含在 Cursor 对象中返回给您。

SQLiteDatabase db = dbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    
    BaseColumns._ID,
    FeedEntry.COLUMN_NAME_TITLE,
    FeedEntry.COLUMN_NAME_SUBTITLE
    };

// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = {
     "My Title" };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";

Cursor cursor = db.query(
    FeedEntry.TABLE_NAME,   // The table to query
    projection,             // The array of columns to return (pass null to get all)
    selection,              // The columns for the WHERE clause
    selectionArgs,          // The values for the WHERE clause
    null,                   // don't group the rows
    null,                   // don't filter by row groups
    sortOrder               // The sort order
    );

第一个参数为被查询表的名称,第二个 参数是一个包含 查询字段名称的 字符串数组。

第三个参数和第四个参数(selectionselectionArgs)会合并起来创建一个 WHERE 子句。由于这两个参数是与选择查询分开提供的,因此它们在合并之前会进行转义。这样一来,您的选择语句就不受 SQL 注入的影响。如需详细了解所有参数,请参阅 query() 参考。

Cursor 对象

Cursor 类型是什么样的数据?

query() 方法返回的 Cursor 对象包含 查询到的结果集,其实就是一组数据而已,可以想象成二维表格,有很多行且每行 又表示一个实体的信息。

如需查看光标中的某一行,请使用 Cursor move 方法之一,您必须始终在开始读取值之前调用该方法。由于光标从位置 -1 开始,因此调用 moveToNext() 会将“读取位置”置于结果的第一个条目上,并返回光标是否已经过结果集中的最后一个条目。对于每一行,您都可以通过调用 Cursor get 方法之一(例如 getString()getLong())读取列的值。对于每个 get 方法,您必须传递所需列的索引位置,您可以通过调用 getColumnIndex()getColumnIndexOrThrow() 获取该位置。遍历结果之后,请对光标调用 close() 以释放其资源。例如,以下代码展示了如何获取存储在光标中的所有项目 ID 并将其添加到列表中:

List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
    
  long itemId = cursor.getLong(
      cursor.getColumnIndexOrThrow(FeedEntry._ID));
  itemIds.add(itemId);
}
cursor.close();

查询并显示宠物信息

下面将在 Pets 应用中 使用 query() 方法查询数据,再通过返回 的 Cursor 对象 读取查询到的内容,并显示到主界面 CatalogActivity 中。

查询并显示宠物的代码可以写在 displayDatabaseInfo() 辅助方法中,因此这个方法原本就是 查询 数据库记录数的方法。

首先将原本的 rawQuery() 查询方法,改为使用 query() 查询所有 宠物信息:

    /**
     * Temporary helper method to display information in the onscreen TextView about the state of
     * the pets database.
     */
    private void displayDatabaseInfo() {
    
        // Create and/or open a database to read from it
        SQLiteDatabase db = mDbHelper.getReadableDatabase();

        // Define a projection that specifies which columns from the database
        // you will actually use after this query.
        String[] projection = {
    
                BaseColumns._ID,
                PetEntry.COLUMN_PET_NAME,
                PetEntry.COLUMN_PET_BREED,
                PetEntry.COLUMN_PET_GENDER,
                PetEntry.COLUMN_PET_WEIGHT,
        };

        // Perform a query on the pets table
        Cursor cursor = db.query(
                PetEntry.TABLE_NAME,           // The table to query
                projection,                    // The array of columns to return (pass null to get all)
                null,                // The columns for the WHERE clause
                null,            // The values for the WHERE clause
                null,                // don't group the rows
                null,                  // don't filter by row groups
                null                  // The sort order
        );
        
        TextView displayView = (TextView) findViewById(R.id.text_view_pet);

        try {
    
            ………………
        } finally {
    
            // Always close the cursor when you're done reading from it. This releases all its
            // resources and makes it invalid.
            cursor.close();
        }
    }

在 try 语句中读取 查询到的所有宠物信息,并显示到 主界面中:

        try {
    
            // Create a header in the Text View that looks like this:
            //
            // The pets table contains <number of rows in Cursor> pets.
            // _id - name - breed - gender - weight
            //
            // In the while loop below, iterate through the rows of the cursor and display
            // the information from each column in this order.
            displayView.setText("Number of rows in pets database table: " + cursor.getCount() + "\n\n");
            displayView.append(String.format("%s - %s - %s - %s - %s\n",
                    PetEntry._ID, PetEntry.COLUMN_PET_NAME, PetEntry.COLUMN_PET_BREED,
                    PetEntry.COLUMN_PET_GENDER, PetEntry.COLUMN_PET_WEIGHT));

            // Figure out the index of each column
            int idColumnindex = cursor.getColumnIndexOrThrow(PetEntry._ID);
            int nameColumnindex = cursor.getColumnIndexOrThrow(PetEntry.COLUMN_PET_NAME);
            int breedColumnindex = cursor.getColumnIndexOrThrow(PetEntry.COLUMN_PET_BREED);
            int genderColumnindex = cursor.getColumnIndexOrThrow(PetEntry.COLUMN_PET_GENDER);
            int weightColumnindex = cursor.getColumnIndexOrThrow(PetEntry.COLUMN_PET_WEIGHT);

            // Iterate through all the returned rows in the cursor
            while (cursor.moveToNext()) {
    
                // Use that index to extract the String or Int value of the word
                // at the current row the cursor is on.
                int currentID = cursor.getInt(idColumnindex);
                String currentName = cursor.getString(nameColumnindex);
                String currentBreed = cursor.getString(breedColumnindex);
                int currentGender = cursor.getInt(genderColumnindex);
                int currentWeight = cursor.getInt(weightColumnindex);
                // Display the values from each column of the current row in the cursor in the TextView
                displayView.append(String.format("%d - %s - %s - %d - %d\n",
                        currentID, currentName, currentBreed, currentGender, currentWeight));
            }
        }
  • 9 行,提示查询到的总记录数量。
  • 10 ~ 12 行,打印表头
  • 15 ~ 19 行,获取每个字段 在 查询到的结果集中的 列索引位置
  • 22 ~ 33 行,读取每行 数据的所有字段,并显示(由于行索引是从 -1 (表头)开始的,所以当首次 调用 cursor.moveToNext() 方法时,就会 转到 索引 为 0 的第一行数据中)

更改完成后运行应用,代码更改前后差异对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xryHt3b4-1612345581158)(.\Day16~2021-02-02.assets\Video_20210203_051845_966.gif)]

总结

成功向 Android 应用中添加了 SQLiteDatabase,学习了如何向数据库输入数据和查询数据,以便在用户界面显示数据,尽管比较简陋。

通过创建 PetDbHelper 类(继承自 SQLiteOpenHelper)来创建和管理数据库,还使用了Android 框架类 例如 SQLiteDatabase、ContentValues 和 Cursor。

参考

想知道为何数据库经常用圆柱体来表示?详情请参阅此处

Save data using SQLite | Android Developers

ContentValues | Android Developers

SQLiteDatabase.insert()

SQLiteDatabase.query()

Cursor | Android Developers

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45075891/article/details/113616228

智能推荐

BGP的选路规则,过滤,重发布_灰灰鱼的博客-程序员宅基地

选路原则:选路的前提是路由必须是优的Bgp表的不优的情况:1.bgp路由表中路由的下一跳不可达(递归失败)2.如果开启了bgp同步,在没有同步的情况下bgp的路由不优(默认是关闭的)开启同步为了避免黑洞,开启反而有害,路由不能到达目标,检查bgp中有没有igp的路由r标记的路由,表示在路由表中不优7.管路距离 20&gt;2008.bgp默认负载均衡条目数是1,意味着默认它没有负...

Mybatis第三天动态Sql语句、XML中一对多、多对一、多对多该怎么写_惨绿少年kb的博客-程序员宅基地

Mybatis第三天Mybatis中使用unpooled配置连接池原理分析Mybatis中使用pooled配置连接的原理分析Mybatis中的事务原理和自动提交设置Mybatis中的动态sql语句if标签where标签foreach标签sql标签,用于抽取重复的语句第三章 Mybatis的多表关联查询一对多查询多对多用户实体类角色实体类角色的单表操作获取角色下的所有用户信息多个表之间的关联从一个用户到多个角色今天内容很重要把Mybatis里面的几个重要标签再解释一下1、Mybatis中的配置类主要分

前端开发框架选型清单_weixin_30917213的博客-程序员宅基地

http://www.infoq.com/cn/news/2014/05/web-ui-framework随着Web技术的不断发展,前端开发框架层出不穷,各有千秋,开发者在做技术选型时总是要费一番脑筋,最近,IBM高级工程师王芳侠撰文对Bootstrap、jQuery UI、jQuery Mobile、Sencha ExtJS、Sencha Touch、Sencha GXT、Dojo、Dojo...

恶意扩展瞄准加密货币交易平台 滥用Facebook Messenger进行传播_weixin_34240657的博客-程序员宅基地

Facebook、Chrome和加密货币用户最近应该注意一款名为“FacexWorm”的Chrome扩展,这个恶意扩展正在通过Facebook Messenger传播,其目的是窃取网站登录凭证、窃取加密货币资金、运行加密矿工脚本以及发送垃圾邮件给受影响Facebook用户的好友。趋势科技的研究人员于4月底发现了这个恶意Chrome扩展,它似乎与另外两起Facebook Messenger垃圾邮件...

宇宙单向度演化_电气工程研习社的博客-程序员宅基地

看待一个学说,我们应该爬到上面去俯视他,虽然登顶的过程很辛苦,但是唯有这样,我们才能对这个学说更了解,看清他的全貌,而不应该一知半解的站在他下面,仰视他,看不清他的全貌,然后人云亦云,别人稍微有一点观点不符合你的固有观点,就一口否定它。 癌细胞其实就是想像辩证法一样走回头路,它是一个尝试者,它原本早已高度分化的上皮细胞,高分化的细胞功能单一,增值能力下降,在人体组织里扮演者无足轻重的乏味角色,远不如低分化的胚胎细胞或干细胞那样,可以自由伸展,大量繁殖,并进而演进成多种类型的功能...

Tomcat - 导入源码到Eclipse_ka_ka_you的博客-程序员宅基地

下载源码用Ant编译下载源码下载源码版 用Ant编译参考

随便推点

Android截屏_hnbyboy的博客-程序员宅基地

本篇文章主要介绍了关于Android方面的截屏操作,下面是主要的代码,最近也思考一个问题,怎么实现一个截屏后,把图片发送至一个服务器端呢,正在思考中希望有哪位有识之士给解答,有源码的更好,谢谢了!!Android截屏代码如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package

Xmind 8 pro 软件破解版_举个栗子gcq的博客-程序员宅基地

https://blog.csdn.net/qq_16093323/article/details/80967867

java将office文档pdf文档转换成swf文件在线预览_weixin_34261415的博客-程序员宅基地

java将office文档pdf文档转换成swf文件在线预览第一步,安装openoffice.org openoffice.org是一套sun的开源office办公套件,能在widows,linux,solaris等操作系统上执行。主要模块有writer(文本文档),impress(演示文稿),Calc(电子表格),Draw(绘图),Math(公...

两个项目能用一个服务器么,一个云服务器能上两个项目_MaxWhut2017的博客-程序员宅基地

一个云服务器能上两个项目 内容精选换一换快速入门以具体场景为例,指引您使用共享型负载均衡快速创建一个负载均衡实例,将访问请求分发到两台弹性云服务器上。共享型负载均衡就是原增强型负载均衡。基础版:适用于业务有大量访问请求,需要通过ELB实例将访问流量分发到两台弹性云服务器进行处理,实现业务流量的负载分担。同时,通过配置健康检查,负载均衡实例可以监控弹性云服务器的运行状况,自动将访如果您已经创建了一台...

2018.09.26洛谷P3957 跳房子(二分+单调队列优化dp)_SC.ldxcaicai的博客-程序员宅基地

传送门表示去年考普及组的时候失了智,现在看来并不是很难啊。直接二分答案然后单调队列优化dp检验就行了。注意入队和出队的条件。代码:#include&amp;lt;bits/stdc++.h&amp;gt;#define N 500005using namespace std;inline int read(){ int ans=0,w=1; char ch=getchar(); while(...

未找到入口 app.json 文件,或者文件读取失败,请检查后重新编译_Hugo.W的博客-程序员宅基地

刚创建好的显示第一个小程序缺少app.json文件右键创建一个名为app.json文件,并填写如下内容{"pages" : ["pages/welcome/welcome"]}根据小程序官方文档,数组的第一项代表小程序的初始页面(首页)。小程序中新增/减少页面,都需要对 pages 数组进行修改。如果不填写相关内容,会显示报错信息:VM353:1 undefinedE...