Android Studio根据RadioGroup选中项向SQLite分表插入数据异常
SQLite多表插入失败问题排查与修复
核心错误点
一、数据库定义类(DBSQLiteHelper)问题
- 建表语句错误给表名包裹了单引号:SQLite中单引号是字符串值的边界符,不是标识符边界,最终创建的表名会自带单引号,后续用无引号的表名执行插入根本找不到对应表。
- 分表结构与外键逻辑错误:已读、待读两张分表的主键设为
id_leidos/id_pendientes并关联主表id,但插入分表时从未给该主键字段传值,主键非空约束+外键约束会直接拦截插入操作。 - 数据库配置常量完全未生效:类中定义的库名常量为
LIBROS、版本号为1,但Activity中初始化Helper时传入的库名是librosTotales、版本号是2,实际打开的是完全无关的新数据库,和初始化建表的库不是同一个。 - 删表逻辑无容错:
onUpgrade中执行DROP TABLE未加IF EXISTS判断,升级时如果表不存在会直接抛异常中断执行。
二、Activity业务逻辑问题
- 控件完全未绑定:类中声明的作者、标题、备注输入框、RadioGroup变量,从未通过
findViewById和布局控件绑定,变量全为null,不仅拿不到用户输入值,RadioGroup的选中状态判断也永远不生效,阅读状态标记变量默认恒为false。 - ContentValues复用未清理:把ContentValues定义在onCreate中全局复用,插入主表时存入的
leidoono字段会一直残留,而两张分表根本没有这个字段,insert方法遇到不存在的列会直接返回-1插入失败。 - 分表插入缺必填值:分表主键关联主表id,但插入主表后从未获取新插入行的id,也没有给分表主键字段传值,直接触发约束报错。
- 表名大小写错误:注释中插入待读表时写的表名是
LibrosPendientes(首字母大写),和定义的小写开头表名不一致,SQLite默认表名大小写敏感,写错表名无法匹配。 - 字段类型不匹配:主表
leidoono字段定义为BOOLEAN类型(SQLite中本质是INTEGER,存0/1),但代码中存入的是"Leído"/"No Leído"字符串,存在类型兼容问题。
三、布局匹配问题
- 布局中作者输入框ID为
@+id/Autor、标题输入框ID为@+id/Titulo、备注输入框ID为@+id/edicion_text,和代码中读取的控件ID不匹配,且RadioGroup未设置ID,无法通过代码获取选中状态。
修复后代码
1. 修正后的DBSQLiteHelper
package Programa import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper class DBSQLiteHelper (context: Context?) : SQLiteOpenHelper(context, NAME_DATABASE, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase) { // 去掉表名单引号,布尔类型字段用INTEGER存0/1 db.execSQL( "CREATE TABLE $librosTotales ( " + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "titulo TEXT NOT NULL," + "autor TEXT NOT NULL," + "comentario TEXT," + "leidoono INTEGER)" ) // 分表主键直接复用主表id,简化外键逻辑 db.execSQL( "CREATE TABLE $librosLeidos ( " + "id INTEGER PRIMARY KEY REFERENCES $librosTotales(id)," + "titulo TEXT NOT NULL," + "autor TEXT NOT NULL," + "comentario TEXT)" ) db.execSQL( "CREATE TABLE $librosPendientes ( " + "id INTEGER PRIMARY KEY REFERENCES $librosTotales(id)," + "titulo TEXT NOT NULL," + "autor TEXT NOT NULL," + "comentario TEXT)" ) // 开启外键约束 db.execSQL("PRAGMA foreign_keys = ON;") } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { // 加IF EXISTS避免删不存在的表报错,先删子表再删主表避免外键拦截 db.execSQL("DROP TABLE IF EXISTS $librosLeidos") db.execSQL("DROP TABLE IF EXISTS $librosPendientes") db.execSQL("DROP TABLE IF EXISTS $librosTotales") onCreate(db) } companion object { // 升级版本号为2,触发旧库表结构更新 private const val DATABASE_VERSION = 2 private const val NAME_DATABASE = "LIBROS" const val librosTotales = "librosTotales" const val librosLeidos = "librosLeidos" const val librosPendientes = "librosPendientes" } }
2. 修正后的Activity核心逻辑
首先给布局里的RadioGroup补上ID属性:android:id="@+id/radioGroupLeido"
package Programa import android.content.ContentValues import android.content.Context import android.database.sqlite.SQLiteDatabase import android.os.Bundle import android.util.Log import android.view.inputmethod.InputMethodManager import android.widget.Button import android.widget.EditText import android.widget.RadioGroup import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.example.organizarlibros.R class annadirLibro : AppCompatActivity() { private lateinit var autor: EditText private lateinit var titulo: EditText private lateinit var comentario: EditText private lateinit var leidoono: RadioGroup private lateinit var con: DBSQLiteHelper override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_annadir_libro) // 绑定所有控件,ID和布局完全对应 autor = findViewById(R.id.Autor) titulo = findViewById(R.id.Titulo) comentario = findViewById(R.id.edicion_text) leidoono = findViewById(R.id.radioGroupLeido) val buttonAnnadir = findViewById<Button>(R.id.buttonAnnadir) con = DBSQLiteHelper(this) buttonAnnadir.setOnClickListener { val autorStr = autor.text.toString().trim() val tituloStr = titulo.text.toString().trim() val comentarioStr = comentario.text.toString().trim() // 基础输入校验 if (autorStr.isEmpty() || tituloStr.isEmpty()) { Toast.makeText(this, "作者和标题不能为空", Toast.LENGTH_SHORT).show() return@setOnClickListener } if (leidoono.checkedRadioButtonId == -1) { Toast.makeText(this, "请选择阅读状态", Toast.LENGTH_SHORT).show() return@setOnClickListener } val isRead = leidoono.checkedRadioButtonId == R.id.leido val db = con.writableDatabase // 开事务保证主表分表插入原子性,避免数据不一致 db.beginTransaction() try { val mainValues = ContentValues().apply { put("autor", autorStr) put("titulo", tituloStr) put("comentario", comentarioStr) put("leidoono", if (isRead) 1 else 0) } // 插入主表,获取自增主键id val mainRowId = db.insert(DBSQLiteHelper.librosTotales, null, mainValues) if (mainRowId == -1L) throw Exception("主表插入失败") val subValues = ContentValues().apply { put("id", mainRowId) // 分表主键传主表id,满足外键约束 put("autor", autorStr) put("titulo", tituloStr) put("comentario", comentarioStr) } val subInsertResult = if (isRead) { db.insert(DBSQLiteHelper.librosLeidos, null, subValues) } else { db.insert(DBSQLiteHelper.librosPendientes, null, subValues) } if (subInsertResult == -1L) throw Exception("分表插入失败") db.setTransactionSuccessful() Toast.makeText(this, "书籍添加成功", Toast.LENGTH_LONG).show() // 清空输入框 autor.text.clear() titulo.text.clear() comentario.text.clear() leidoono.clearCheck() } catch (e: Exception) { Log.e("DB_ERROR", "插入失败", e) Toast.makeText(this, "添加失败:${e.message}", Toast.LENGTH_SHORT).show() } finally { db.endTransaction() } // 关闭软键盘 val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(buttonAnnadir.windowToken, 0) } } }
注意:改完代码后先卸载手机上的旧版App再重新运行,清空本地残留的错误表结构,否则新的表定义不会生效。
内容的提问来源于stack exchange,提问作者Arcajp
相关产品推荐
相关产品推荐

