android学习笔记----SQLite数据库

news2024/11/17 3:28:42

用SQLite语句执行:

首先看到界面:

img

代码如下:

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import com.example.createdb2.dao.ContactInfoDao;

public class MainActivity extends AppCompatActivity {

    private EditText et_name;
    private EditText et_phone;
    private ContactInfoDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 1.找到需要用到的控件
        et_name = (EditText) findViewById(R.id.et_name);
        et_phone = (EditText) findViewById(R.id.et_phone);
        // 2.new一个Dao出来
        dao = new ContactInfoDao(this, "mydb.db", null, 1);
    }

    /**
     * 添加一条联系人信息
     *
     * @param view
     */
    public void add(View view) {
        // 做具体的添加操作
        String name = et_name.getText().toString().trim();
        String phone = et_phone.getText().toString().trim();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(phone)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
            return;
        } else {
            dao.add(name, phone);
            Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 删除一条联系人信息
     *
     * @param view
     */
    public void delete(View view) {
        String name = et_name.getText().toString().trim();
        if (TextUtils.isEmpty(name)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
            return;
        } else {
            dao.delete(name);
            Toast.makeText(this, "删除成功", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 修改联系人号码
     *
     * @param view
     */
    public void update(View view) {
        String name = et_name.getText().toString().trim();
        String phone = et_phone.getText().toString().trim();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(phone)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
            return;
        } else {
            dao.update(phone, name);
            Toast.makeText(this, "修改成功", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 查询联系人号码
     *
     * @param view
     */
    public void query(View view) {
        String name = et_name.getText().toString().trim();
        if (TextUtils.isEmpty(name)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
            return;
        } else {
            String phone = dao.query(name);
            if (phone != null) {
                Toast.makeText(this, "查询到的号码为:" + phone, Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "无此联系人信息", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

ContactInfoDao.java

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

import com.example.createdb2.MyDBOpenHelper;

public class ContactInfoDao {

    private final MyDBOpenHelper helper;

    public ContactInfoDao(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        helper = new MyDBOpenHelper(context, name, factory, version);
    }

    /**
     * create table aa(id integer primary key autoincrement, name char(20), phone varchar(20));
     * create table temp as select id, name from aa; //
     * temp表没有了PRIMARY KEY AUTOINCREMENT,查看建表语句CREATE TABLE "temp"(id INT,NAME TEXT);
     * integer变成了int
     * char变成text
     * 新表中没有旧表中的primary key,Extra,auto_increment等属性,需要自己手动加,具体参看后面的修改表即字段属性.
     *
     * @param name  联系人姓名
     * @param phone 联系人电话
     */
    public void add(String name, String phone) {
        SQLiteDatabase db = helper.getWritableDatabase(); // 如果数据库已存在就打开,否则创建一个新数据库
        db.execSQL("insert into contactinfo (name, phone) values(?, ?)", new Object[]{name, phone});
        // 记得关闭数据库,释放资源
        db.close();
    }

    /**
     * 删除一条记录
     *
     * @param name 联系人姓名
     */

    public void delete(String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("delete from contactinfo where name = ?", new Object[]{name});
        // 记得关闭数据库,释放资源
        db.close();
    }

    /**
     * 更新一条记录
     *
     * @param name  联系人姓名
     * @param phone 联系人电话
     */

    public void update(String phone, String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL("update contactinfo set phone = ? where name = ?;", new Object[]{phone, name});
        // 记得关闭数据库,释放资源
        db.close();
    }

    /**
     * 查询联系人的电话号码
     *
     * @param name 联系人姓名
     */
    public String query(String name) {
        SQLiteDatabase db = helper.getReadableDatabase();
        Cursor cursor = db.rawQuery("select phone from contactinfo where name = ?", new String[]{name});
        String phone = null;
        if (cursor.moveToNext()) {
            phone = cursor.getString(0);
        }
        // 记得关闭数据库,释放资源
        cursor.close();
        db.close();
        return phone;
    }
}

笔记批注:

SQLiteOpenHelper是个抽象类,里面有2个抽象方法onCreate()和onUpdate(),我们必须在自己的帮助类里面重写这2个方法,然后分别在这两个方法中实现创建、升级数据库逻辑。

SQLiteOpenHelper还有2个非常重要的实例方法getReadableDatabase()和getWritableDatabase()。这两个方法都可以创建或者打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写的对象。

不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法将抛出异常。

构建出SQLiteOpenHelper的实例之后,在调用它的getReadableDatabase()和getWritableDatabase()就能够创建数据库了。数据库文件在/data/data//databases目录下。

sqlite中是不支持删除列操作的,所以网上 alter table [table_name] drop column [col_name] 这个语句在sqlite中是无效的(这不是MySQL),而替代的方法可以如下: 1.根据原表创建一张新表 2.删除原表

3.将新表重名为旧表的名称

慎用create table as select,比如想删除一列phone

create table aa(id integer primary key autoincrement, name char(20), phone varchar(20)); create table temp as select id, name from aa;

新表中没有旧表中的primary key,Extra,auto_increment等属性,需要自己手动加,具体参看后面的修改表即字段属性.

那么新表temp就没了主键,不会自动增长,查看建表语句integer变成了int, char变成text。

只能创建类似于这样给出明确约束的

CREATE TABLE temp(id INTEGER PRIMARY KEY AUTOINCREMENT, NAME CHAR(20));

MyDBOpenHelper.java

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import android.widget.Toast;

public class MyDBOpenHelper extends SQLiteOpenHelper {
    private String TAG = "MyDBOpenHelper";
    private Context mContext;

    // 第一个参数是上下文
    // 第二个参数是数据库名称
    // 第三个参数null表示使用默认的游标工厂
    // 第四个参数是数据库的版本号,数据库只能升级,不能降级,版本号只能变大不能变小
    public MyDBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        // 更改一下版本号会调用onUpgrade
        mContext = context;
    }

    // 当数据库第一次被创建的时候调用的方法,适合在这个方法里面把数据库的表结构定义出来
    // 当app再次启动会发现已经存在mydb.db数据库了,因此不会再创建一次
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG, "数据库被创建了: ");
        // MySQL是AUTO_INCREMENT, SQLite是AUTOINCREMENT
        db.execSQL("CREATE TABLE contactinfo(id INTEGER PRIMARY KEY AUTOINCREMENT, NAME CHAR(20), phone VARCHAR(20));");
        Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
    }

    // 当数据库更新的时候调用的方法
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d(TAG, "数据库被更新了: ");
        //db.execSQL("ALTER TABLE contactinfo ADD account VARCHAR(20);");
        db.execSQL("drop table if exists contactinfo");
        onCreate(db);
    }
}

笔记批注:

当我们重新运行程序时,数据库因为已经存在,不会再次创建,所以这个onCreate方法不会再次调用,怎么办呢?当然卸载程序再次运行也可以,这样的做法比较极端。这里就可以用到SQLiteOpenHelper的升级功能了。

db.execSQL(“drop table if exists contactinfo”);

onCreate(db);

如果存在contactinfo表就删除掉,然后再次调用onCreate方法,如果没有删除直接onCreate,那么系统会发现这张表存在,直接报错。

那么如何让onUpdate()方法能够执行呢?我们这里的MyDBOpenHelper构造器第四个参数是当前数据库的版本号,之前传入的是1,现在只要传入一个比1大的数字即可运行onUpdate方法。

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:hint="请输入联系人的姓名"
        android:inputType="textPersonName"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="12dp"
        android:ems="10"
        android:hint="请输入联系人的电话"
        android:inputType="number"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_name" />

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:onClick="add"
        android:text="添加"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_phone" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:onClick="delete"
        android:text="删除"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button" />

    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:onClick="update"
        android:text="修改"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button2" />

    <Button
        android:id="@+id/button4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="16dp"
        android:onClick="query"
        android:text="查询"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button3" />
</android.support.constraint.ConstraintLayout>

当输入数据然后点击添加的时候数据库会被创建(如果数据库还没被创建),数据会添加成功

我们可以把数据库导出到SQLite Expert去查看表内容,也可以直接在控制台查看一个大概,查看数据库和表有没有被创建。

这里只演示在控制台操作。

在Terminal或者在系统控制台输入adb shell

然后进行如下操作:

img

用cd命令进入到/data/data/com.example.createdb2/databases 目录

注意:7.0及以上的模拟器无法进入com.xxxxxx.xxxxx,没有权限,示例只能在6.0及以下,当然,我们是可以直接找到mydb.db导出,然后用SQLite Expert去查看就更好了。在这里只是演示以下控制台该怎么做。

这个目录中,mydb.db是我们创建的

另一个是mydb.db-journal,这是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小是0字节

接下来输入sqlite3 mydb.db 打开mydb.db数据库

输入.table命令查看数据库中有哪些表,这个android_metadata是每个数据库中都会自动生成的,不用管。

另一张contactinfo是我们在MyDBOpenHelper中创建的。

接着可以用.schema命令查看它们的建表语句。

最后可以输入.exit或.quit命令退出数据库的编辑,再键入exit就可以退出设备控制台了。

img

img

也可以直接写sql语句查询,如图

img

img

这里数据库版本是2

补充知识点:改变dos编码方式:chcp 936 //变成GBK编码

chcp 65001 //变成UTF-8编码

利用SQLiteDatabase中自带的增删改查操作:

ContactInfoDao.java

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.example.createdb3.MyDBOpenHelper;

public class ContactInfoDao {

    private final MyDBOpenHelper helper;
    private String TAG = "ContactInfoDao";

    public ContactInfoDao(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        helper = new MyDBOpenHelper(context, name, factory, version);
    }

    /**
     * create table aa(id integer primary key autoincrement, name char(20), phone varchar(20));
     * create table temp as select id, name from aa; //
     * temp表没有了PRIMARY KEY AUTOINCREMENT,查看建表语句CREATE TABLE "temp"(id INT,NAME TEXT);
     * integer变成了int
     * char变成text
     * 新表中没有旧表中的primary key,Extra,auto_increment等属性,需要自己手动加,具体参看后面的修改表即字段属性.
     * 添加一条记录
     *
     * @param name  联系人姓名
     * @param phone 联系人电话
     * @return 返回的是添加在数据库的行号,-1代表失败
     */
    public long add(String name, String phone) {
        SQLiteDatabase db = helper.getWritableDatabase(); // 如果数据库已存在就打开,否则创建一个新数据库
        // db.execSQL("insert into contactinfo (name, phone) values(?, ?)", new Object[]{name, phone});
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("phone", phone);
        long rowId = db.insert("contactinfo", null, values);

        // 记得关闭数据库,释放资源
        db.close();
        return rowId;
    }

    /**
     * 根据姓名删除一条记录
     *
     * @param name 联系人姓名
     * @return 返回0代表的是没有做任何记录,返回的整数int值代表删除了几条数据
     */

    public int delete(String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        // db.execSQL("delete from contactinfo where name = ?", new Object[]{name});
        int rowId = db.delete("contactinfo", "name=?", new String[]{name});
        // 记得关闭数据库,释放资源
        db.close();
        return rowId;
    }

    /**
     * 修改联系人电话号码
     *
     * @param name  联系人姓名
     * @param phone 联系人新电话
     * @return rowId代表更新了多少行记录
     */

    public int update(String phone, String name) {
        SQLiteDatabase db = helper.getWritableDatabase();
        // db.execSQL("update contactinfo set phone = ? where name = ?;", new Object[]{phone, name});
        ContentValues values = new ContentValues();
        values.put("phone", phone);
        int rowId = db.update("contactinfo", values, "name = ?", new String[]{name});
        // 记得关闭数据库,释放资源
        db.close();
        return rowId;
    }

    /**
     * 查询联系人的电话号码
     *
     * @param name 联系人姓名
     * @return 电话号码
     */
    public Cursor query(String name) {
        SQLiteDatabase db = helper.getReadableDatabase();
        // Cursor cursor = db.rawQuery("select phone from contactinfo where name = ?", new String[]{name});
        /*Cursor cursor = db.query("contactinfo", new String[]{"phone"}, "name = ?", new String[]{name}, null, null, null);
        String phone = null;
        if (cursor.moveToNext()) {
            phone = cursor.getString(0);
        }*/
        Cursor cursor = db.query("contactinfo", null, "name = ?", new String[]{name}, null, null, null);
        // 记得关闭数据库,释放资源
        // db.close();// 当用ContentProvider返回一个Cursor时,db是不能关闭的
        // 否则抛出java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.
        return cursor;
    }
}

再把MainActivity.java里面的query()方法改掉就行了

/**
     * 查询联系人号码
     *
     * @param view
     */
    public void query(View view) {
        String name = et_name.getText().toString().trim();
        if (TextUtils.isEmpty(name)) {
            Toast.makeText(this, "不能为空", Toast.LENGTH_SHORT).show();
            return;
        } else {
            Cursor cursor = dao.query(name);
            String phone = null;
            StringBuffer str = new StringBuffer();
            if (cursor.moveToFirst()) { // 将光标移动到第一行,如果游标为空,此方法将返回false。
                String str1 = null;
                do {
                    phone = cursor.getString(cursor.getColumnIndex("phone"));
                    str1 = "name:" + name + " phone:" + phone;
                    Log.d(TAG, str1);
                    str.append(str1 + "\n");
                } while (cursor.moveToNext());// 将光标移动到下一行,如果游标已经超过结果集中的最后一个条目,此方法将返回false。
                str.deleteCharAt(str.length() - 1); // StringBuffer没有trim()
            }
            cursor.close();
            if (phone != null) {
                Toast.makeText(this, "查询到的联系人信息为:\n" + str, Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "无此联系人信息", Toast.LENGTH_SHORT).show();
            }
        }
    }

img

添加同一个人多次时可以一次查出来。

注意:当用ContentProvider返回一个Cursor时,db是不能关闭的,否则抛出异常java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.

笔记批注:

public long insert (String table, String nullColumnHack, ContentValues values)

参数介绍:

table: 要插入数据的表的名称

nullColumnHack:当values参数为空或者里面没有内容的时候,我们insert是会失败的(底层数据库不允许插入一个空行),为了防止这种情况,我们要在这里指定一个列名,到时候如果发现将要插入的行为空行时,就会将你指定的这个列名的值设为null,然后再向数据库中插入。

values:一个ContentValues对象,类似一个map.通过键值对的形式存储值。

这里很多人会迷惑,nullColumnHack到底干什么用的,为什么会出现呢。当我们不设定一列的时候,不都是数据库给设为默认值吗?很多字段设置默认值也是null,这里显示的设置也是null,有什么区别吗,怎么会显示设置了之后就允许插入了呢?

其实在底层,各种insert方法最后都回去调用insertWithOnConflict方法,这里我们粘贴出该方法的部分实现

   /** 
    * General method for inserting a row into the database. 
    * 
    * @param table the table to insert the row into 
    * @param nullColumnHack SQL doesn't allow inserting a completely empty row, 
    *            so if initialValues is empty this column will explicitly be 
    *            assigned a NULL value 
    * @param initialValues this map contains the initial column values for the 
    *            row. The keys should be the column names and the values the 
    *            column values 
    * @param conflictAlgorithm for insert conflict resolver 
    * @return the row ID of the newly inserted row 
    * OR the primary key of the existing row if the input param 'conflictAlgorithm' = 
    * {@link #CONFLICT_IGNORE} 
    * OR -1 if any error 
    */  
   public long insertWithOnConflict(String table, String nullColumnHack,  
           ContentValues initialValues, int conflictAlgorithm) {  
       if (!isOpen()) {  
           throw new IllegalStateException("database not open");  
       }  
  
       // Measurements show most sql lengths <= 152  
       StringBuilder sql = new StringBuilder(152);  
       sql.append("INSERT");  
       sql.append(CONFLICT_VALUES[conflictAlgorithm]);  
       sql.append(" INTO ");  
       sql.append(table);  
       // Measurements show most values lengths < 40  
       StringBuilder values = new StringBuilder(40);  
  
       Set<Map.Entry<String, Object>> entrySet = null;  
       if (initialValues != null && initialValues.size() > 0) {  
           entrySet = initialValues.valueSet();  
           Iterator<Map.Entry<String, Object>> entriesIter = entrySet.iterator();  
           sql.append('(');  
  
           boolean needSeparator = false;  
           while (entriesIter.hasNext()) {  
               if (needSeparator) {  
                   sql.append(", ");  
                   values.append(", ");  
               }  
               needSeparator = true;  
               Map.Entry<String, Object> entry = entriesIter.next();  
               sql.append(entry.getKey());  
               values.append('?');  
           }  
  
           sql.append(')');  
       } else {  
           sql.append("(" + nullColumnHack + ") ");  
           values.append("NULL");  
       }  

这里我们可以看到,当我们的ContentValues类型的数据initialValues为null,或者size<=0时,就会再sql语句中添加nullColumnHack的设置。我们可以想象一下,如果我们不添加nullColumnHack的话,那么我们的sql语句最终的结果将会类似insert into tableName()values();这显然是不允许的。而如果我们添加上nullColumnHack呢,sql将会变成这样,insert into tableName (nullColumnHack)values(null);这样很显然就是可以的。

public int delete (String table, String whereClause, String[] whereArgs)

删除数据库中行的方便方法。

table:要从其中删除的表

whereClause:删除时要应用的可选WHERE子句。传递NULL将删除所有行。

whereArgs:您可以在WHERE子句中包括?s,该子句将由WHERE Args的值替换。这些值将被绑定为String。

public int update (String table, ContentValues values, String whereClause, String[] whereArgs)

更新数据库中行的方便方法。

table:要更新的表

values:从列名到新列值的映射。NULL是将被转换为NULL的有效值。

whereClause:更新时要应用的可选WHERE子句。传递NULL将更新所有行。

whereArgs: 您可以在WHERE子句中包括?s,该子句将由WHERE Args的值替换。这些值将被绑定为String。

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)

查询给定的URL,返回Cursor结果集。

table:要编译查询的表名。

columns:返回哪些列的列表。传递NULL将返回所有列,这是不鼓励的,以防止从存储区读取不被使用的数据。

selection:一个过滤器,声明要返回的行,格式化为SQLWHERE子句(不包括WHERE本身)。传递NULL将返回给定表的所有行。

selectionArgs:您可以在选择中包括?s,它将被selectionArgs的值替换,以便它们出现在所选内容中。这些值将被绑定为String。

groupBy:一个过滤器,声明如何分组行,格式化为SQL GROUP BY子句(本身不包括组)。传递NULL将导致行不被分组。

having:如果正在使用行分组,则筛选器将声明要在游标中包含哪些行组,格式为SQL HARING子句(不包括HAVING本身)。传递NULL将导致包括所有行组,并且在不使用行分组时是必需的。

orderBy:如何对行进行排序,格式化为SQLOrderBy子句(不包括Order本身)。传递NULL将使用默认排序顺序,排序顺序可能是无序的。

query有4个重载方法,建议查官方api。

SQLite数据库的事务介绍:

MainActivity.java

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private String TAG = "MainActivity";
    private MyOpenHelper helper;
    private EditText editText1, editText2, editText3;
    private String table = "info";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        helper = new MyOpenHelper(this, table, null, 1);
        editText1 = (EditText) findViewById(R.id.editText1);
        editText2 = (EditText) findViewById(R.id.editText2);
        editText3 = (EditText) findViewById(R.id.editText3);
    }

    public String queryColumn(Cursor cursor, String s) {
        String ss = null;
        if (cursor.moveToFirst()) { // 必须moveToFirst()否则异常
            ss = cursor.getString(cursor.getColumnIndex(s));
        }
        return ss;
    }

    public void onclick(View view) {
        SQLiteDatabase db = helper.getReadableDatabase();
        String name1 = editText1.getText().toString().trim();
        String name2 = editText2.getText().toString().trim();
        String str = editText3.getText().toString().trim();
        // 使用事务进行转账
        db.beginTransaction(); // 开启事务
        try {
            Cursor cursor = db.query(table, new String[]{"money"}, "name = ?",
                    new String[]{name1}, null, null, null);
            int money = Integer.valueOf(queryColumn(cursor, "money"));
            // 实现转账的逻辑,实际就是写sql语句
            //db.execSQL("update info set money = money - ? where name = ?", new Object[]{str, name1});
            ContentValues values = new ContentValues();
            int remain = money - Integer.valueOf(str);
            if (remain < 0) {
                Toast.makeText(this, "您的余额不足,转账失败", Toast.LENGTH_SHORT).show();
                return;
            }
            values.put("money", remain + "");
            db.update(table, values, "name = ?", new String[]{name1});

            // int i = 9 / 0; // 让事务回滚示例

            // db.execSQL("update info set money = money + ? where name = ?", new Object[]{str, name2});
            cursor = db.query(table, new String[]{"money"}, "name = ?",
                    new String[]{name2}, null, null, null);
            int money1 = Integer.valueOf(queryColumn(cursor, "money"));
            ContentValues values1 = new ContentValues();
            int remain1 = money1 + Integer.valueOf(str);
            if (remain1 < 0) {
                return;
            }
            values1.put("money", remain1 + "");
            db.update(table, values1, "name = ?", new String[]{name2});

            // 转账之后的cursor
            cursor = db.query(table, new String[]{"money"}, "name = ?",
                    new String[]{name1}, null, null, null);
            String query1 = queryColumn(cursor, "money");
            cursor = db.query(table, new String[]{"money"}, "name = ?",
                    new String[]{name2}, null, null, null);
            String query2 = queryColumn(cursor, "money");
            cursor.close();
            Log.d(TAG, name1 + "账户余额:" + query1 + "\n");
            Log.d(TAG, name2 + "账户余额:" + query2 + "\n");
            Toast.makeText(this, name1 + "账户余额:" + query1 + "\n" + name2 + "账户余额:" + query2, Toast.LENGTH_LONG).show();
            // 给当前事务设置一个成功的标记
            db.setTransactionSuccessful();
        } catch (Exception e) { // 有catch不至于程序崩溃
            Toast.makeText(this, "服务器忙,请稍后再试", Toast.LENGTH_SHORT).show();
        } finally {
            db.endTransaction(); // 关闭事务,如果未执行setTransactionSuccessful,则回滚
        }
    }
}

MyOpenHelper.java

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

public class MyOpenHelper extends SQLiteOpenHelper {
    private String TAG = "MyOpenHelper";
    public MyOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    /**
     * 当数据库第一次创建时调用,特别适合用于表的初始化
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        Log.d(TAG, "数据库被创建了,onCreate里面开始建表 ");
        db.execSQL("create table info (_id integer primary key autoincrement, name varchar(20), phone varchar(20), money varchar(20))");
        db.execSQL("insert into info ('name', 'phone', 'money') values('zhangsan', '138888', '2000')");
        db.execSQL("insert into info ('name', 'phone', 'money') values('lisi', '139999', '4000')");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("drop table if exists info");
        onCreate(db);
    }
}

activity_mainxml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:onClick="onclick"
        android:text="转账"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editText3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:hint="请输入转账金额"
        android:inputType="number"
        app:layout_constraintBottom_toTopOf="@+id/button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText2" />

    <EditText
        android:id="@+id/editText2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:hint="请输入收款人姓名"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/editText1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/editText3" />

    <EditText
        android:id="@+id/editText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="8dp"
        android:ems="10"
        android:hint="请输入转账人姓名"
        android:inputType="textPersonName"
        app:layout_constraintBottom_toTopOf="@+id/editText2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

img

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1411131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot_基础

学习目标 基于SpringBoot框架的程序开发步骤 熟练使用SpringBoot配置信息修改服务器配置 基于SpringBoot的完成SSM整合项目开发 一、SpringBoot简介 1. 入门案例 问题导入 SpringMVC的HelloWord程序大家还记得吗&#xff1f; SpringBoot是由Pivotal团队提供的全新框架&…

2024年前端会流行什么技术和框架了?

分享下一款面向研发开发使用、全源码支持、前后端一体的低代码工具JNPF&#xff0c;是引迈信息明星项目&#xff0c;拥有强大的可视化建模、数据库和API集成能力。 目前已有将近超千家企业将JNPF低代码开发工具融入内部研发体系&#xff0c;相较于传统的产研开发&#xff0c;使…

【oracle】oracle客户端及oracle连接工具

一、关于oracle客户端 1.1 Oracle Client 完整客户端 包含完整的客户端连接工具。 包很大&#xff0c;需要安装 1.2 instantclient 即时客户端 是 Oracle(R) 发布的轻量级数据库客户端&#xff0c;减少甚至只包含几个文件&#xff0c;您无需安装标准的客户端&#xff0c;就可以…

百度Apollo | 实车自动驾驶:感知、决策、执行的无缝融合

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《linux深造日志》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! ⛳️ 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

MongoDB相关概念

1.1 业务应用场景 传统的关系型数据库&#xff08;如MySQL&#xff09;&#xff0c;在数据操作的“三高”需求以及应对Web2.0的网站需求面前&#xff0c;显得力不从心。“三高”需求&#xff1a; High performance - 对数据库高并发读写的需求。Huge Storage - 对海量数据的高…

论文阅读《thanking frequency fordeepfake detection》

这篇论文从频域的角度出发&#xff0c;提出了频域感知模型用于deepfake检测的模型 整体架构图&#xff1a; 1.FAD&#xff1a; 频域感知分解&#xff0c;其实就是利用DCT变换&#xff0c;将空间域转换为频域&#xff0c;变换后的图像低频信息在左上角&#xff0c;高频信息在右…

移动端短视频SDK,企业级视频编辑解决方案

短视频已成为企业营销、品牌推广、信息传递的重要手段&#xff0c;美摄科技凭借其在视频处理领域的深厚积累&#xff0c;推出了全方位的移动端短视频SDK方案&#xff0c;为企业提供从拍摄、特效、人脸道具到快速编辑的一站式解决方案。 1、拍摄与录制 美摄科技移动端短视频SD…

直播间相关基础

1.直播前要搞懂的词 2.自然流量的来源 自然流量&#xff1a;提升ROI的关键 GPMCTRCVR客单价 3.影响自然流量的因素 4.提升自然流量 * 5.直播间产品的搭配&#xff08;三款&#xff09; 引流款 主推款&#xff08;爆款&#xff09;利润款 直播间的形式 绿幕/投屏影棚

cartographer离线建图报错:data_.trajectory_nodes.SizeOfTrajectoryOrZero

cartographer离线建图报错: data_.trajectory_nodes.SizeOfTrajectoryOrZero [FATAL] [1706177325.876019302, 1706015603.398505596]: F0125 18:08:45.000000 17607 pose_graph_2d.cc:1314] Check failed: data_.trajectory_nodes.SizeOfTrajectoryOrZero(trajectory_id) &…

力扣刷MySQL-第八弹(详细讲解)

&#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &#x1f388;该系列文章专栏&#xff1a;力扣刷题讲解-MySQL &#x1f379;文章作者技术和水平很有限&#xff0c;如果文中出…

css 中 flex 布局最后一行实现左对齐

问题 flex 布局最后一行没有进行左对齐显示&#xff1a; <div classparent><div classchild></div><div classchild></div><div classchild></div><div classchild></div><div classchild></div><div…

Sentinel-1 扩展时序注释数据集 (ETAD)的查询和下载

概述 Sentinel-1的扩展计时注释数据集&#xff08;ETAD&#xff09;是ESA&#xff08;DLR作为承包商&#xff09;开发的新辅助产品&#xff0c;为用户提供校正&#xff0c;将Sentinel-1 SLC图像的几何精度提高到厘米级别。该产品包含分析就绪层&#xff0c;用于消除大气路径延…

win10+elasticsearch8.12 安装教程

Elasticsearch是一种搜索引擎&#xff0c;本地安装完成之后&#xff0c;可使用其他编程语言&#xff08;例如python&#xff09;与elasticsearch建立连接&#xff0c;然后使用python脚本搜索elasticsearch中的数据 1下载 elasticsearch elasticsearch最新版官网下载链接 点击…

最小二乘法和梯度下降法

目录 最小二乘法 梯度下降法 1.梯度下降法的定义 2.梯度下降法的运行过程 3.梯度下降法的步骤 4.梯度下降法的分类 &#xff08;1&#xff09;批量梯度下降 BGD &#xff08;2&#xff09;随机梯度下降 SGD &#xff08;3&#xff09;小批量梯度下降 mini-batch GD 5…

ASP.NET Core NE8实现HTTP Upgrade和HTTP CONNECT代理服务器

看到一个文章[Go] 不到 100 行代码实现一个支持 CONNECT 动词的 HTTP 服务器 原理图如下&#xff1a; 这里在NET8.0中实现反向代理服务器部分 新建MiniApi项目 编辑Program.cs文件。 var builder WebApplication.CreateSlimBuilder(args);var app builder.Build();// 将…

非官方 Bevy 作弊书07-09

源自 网页 Working with 2D - Unofficial Bevy Cheat Book 个人用 有道 翻译&#xff0c;希望能够帮助像我一样的 英语不好 的 bevy 初学者 非官方 Bevy 作弊书 7 使用 bevy 2D 本章涵盖与使用 Bevy 制作 2D 游戏相关的主题。 2D Camera Setup - Unofficial Bevy Cheat Book 非…

【代码随想录14】104.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数

目录 104.二叉树的最大深度题目描述参考代码 111.二叉树的最小深度题目描述参考代码 222.完全二叉树的节点个数题目描述参考代码 104.二叉树的最大深度 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径…

解决 Required Integer parameter ‘uid‘ is not present

1.原因分析 后端没接收到uid可能是前端没传递uid也可能是前端传递了uid&#xff0c;但是传递方式与后端接收方式不匹配&#xff0c;导致没接收到更大的可能是因为后端请求方式错了。比如&#xff1a; 2.解决方案 先确定前端传参方式与后端请求方式是匹配的后端get请求的话…

TIDB修改日志级别

日志级别 tidb 默认日志级别 为 info,可选项为 [debug, info, warn, error, fatal]&#xff0c;在 tidb-server,tikv-server,tiflash-server,pd-server 四个板块内都可以设置 查看集群名称 使用 tiup 命令 查看集群信息 [rootDB2-001 tidb]# tiup cluster list tiup is chec…

【Axure高保真原型】可视化环形图

今天和大家可视化环形图的原型模板&#xff0c;&#xff0c;包括4种效果&#xff0c;移入变色在环形中部显示数据、移入变色在标签弹窗显示数据、移入放大在环形中部显示数据、移入放大在标签弹窗显示数据。这个原型是用Axure原生元件制作的&#xff0c;所以不需要联网或者调用…