您需要在列名之间使用逗号(* 表示所有列,因此不需要 *,city_name)。
即SELECT * FROM city.... 在您的情况下相当于说SELECT id,city_name,state,country,lon,lat FROM city ....
虽然SELECT *,city_name .... 说的是SELECT id,city_name,state,country,lon,lat,city_name FROM city ....
虽然有效的 SQL 为什么两次得到相同的值(修辞)?此外,Room 应该使用哪个(即使它们相同)city_name(rehtorical)? 这是模棱两可的,有可能导致错误(我认为房间会处理这个并选择最后一个)
你想要:-
@Dao
public interface DAO {
//this class contains methods that accesses the database
@Query("SELECT * FROM city WHERE city_name LIKE :cityName")
List<Entity> getCity(String cityName); //<<<< The method that is called from the code
}
这将返回一个列表,即实体对象的列表
或者:-
@Dao
public interface DAO {
//this class contains methods that accesses the database
@Query("SELECT city_name FROM city WHERE city_name LIKE :cityName")
List<String> getCityName(cityName);
}
- 这会返回一个列表,其中每个字符串都是城市名称的
- 你可以两者兼得
在调用时,如果传递的字符串 (cityName) 是“New%”,那么您将得到 New York、Newcastle 等(即 % 是 1 个或多个字符的通配符 _ 对于单个字符)
示例
使用您的代码,稍作改动,上面的代码编译并运行正常。
上面建议的更改已包含(两者),因此使用的 DAO 类是:-
@Dao
public interface DAO {
//this class contains methods that accesses the database
@Query("SELECT * FROM city WHERE city_name LIKE :cityName")
List<Entity> getCitiesByName(String cityName);
@Query("SELECT city_name FROM city WHERE city_name LIKE :cityName")
List<String> getCityNamesByName(String cityName);
}
我对 Database 类 进行了一些更改,以 a) 允许在主线程上运行(添加 .allowMainThreadQueries())和 b) 不从资产文件创建(注释掉 .createFromAsset("city_db.db") ) 根据:-
@androidx.room.Database(entities = Entity.class,version = 1)
//must include the entity associated with database
public abstract class Database extends RoomDatabase {
//must be abstract
public abstract DAO dao();
//must declare this
//check if a database instance is existing
//if not, create one and return it
private static volatile Database db;
static Database getDatabase(final Context context){
if(db==null){
synchronized (Database.class){
if(db == null){
db = Room.databaseBuilder(context.getApplicationContext(),
Database.class, "Database")
//.createFromAsset("city_db.db")
.allowMainThreadQueries()
.build();
}
}
}
return db;
}
}
使用的 MainActivity 是:-
public class MainActivity extends AppCompatActivity {
Database db;
DAO dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Database.getDatabase(this);
dao = db.dao();
dao.getCitiesByName("Somewhere%");
dao.getCityNamesByName("Nowhere%");
}
}
因此,即使没有对结果进行任何处理(数据库内没有数据),上面也会打开数据库(不使用 .createFromAsset 来创建它)。
您可以根据 Android Studio 中的 App Inspection(又名 Database Inspector)看到这一点:-
因此您的代码,禁止资产文件的副本在按照建议进行修改时工作。
存在的问题
根据您的屏幕截图,当涉及到.build() 时,您可能会遇到持续的问题。
房间期望复制的数据库与从实体派生的架构完全匹配(对于@Database 注释的实体参数中定义的实体)。
作为一个例子,屏幕截图显示城市的名称在名为 name 的列(字段)中,而在您的实体中等效的列名,根据实体 Entity 是 city_name .房间构建将失败。 coord.lon 和 coord.lat 也是如此,它们是 lon 和 lat。
此外,列定义(列类型和约束,例如 NOT NULL)也必须匹配,否则 Room 构建将失败。
因此,您必须确保架构匹配。这可能需要使用一种可用的工具来相应地转换数据库(由于 Room 施加的限制,转换正在复制的数据库可能比尝试匹配现有数据库容易得多)。
当您使用包含实体的有效/可用@Database 进行编译时,Room 会生成 SQL。
例如在您的情况下,生成的 java(从 Android Studio 中的 Android View 可见)中的类与具有 @Database 注释但后缀为 _IMPL 的类命名相同。
因此,当您的@Database 类被命名为 Database 时,就会有一个类 Database_IMPL。
在类中会有一个方法createAllTables,它包含所有表的SQL(任何与room_打交道的SQL都可以忽略)。
例如你的会是这样的:-
您很可能需要使用 SQLite 工具转换数据库,例如 DB Browser for SQLite、SQlite Studio、DBeaver、Navicat For SQlite 才能使用架构。
粗略地说,使用从生成的java中获取的SQL来创建表,然后复制数据。
转换本身非常简单。首先创建 city_db.db 文件的副本。
打开任何一个(在您喜欢的 SQLite 工具中,用于演示)
-
我已经使用 Navicat 并使用查询来生成原始示例(只有几个国家)):-
CREATE TABLE IF NOT EXISTS city (id INTEGER PRIMARY KEY, name TEXT,state TEXT, country TEXT, `coord.lon` REAL, `coord.lat`);
INSERT INTO city VALUES
(3904809,'Santa Rita','','BO',-63.3499,-17.966),
(3904890,'Santa Elenta','','BO',-64.7833,-20.5496)
;
- don't use the above
然后使用下面的 SQL 注意这将删除原始表(不想要过大的资产文件)。 如果第二次运行,这将失败,因为原始列名已被有效覆盖,因此原始列名不存在。
DROP TABLE IF EXISTS room_city;
/*As copied from the App's generated java */
/* BUT table name changed to room_city from city */
CREATE TABLE IF NOT EXISTS `room_city` (`id` INTEGER NOT NULL, `city_name` TEXT, `state` TEXT, `country` TEXT, `lon` TEXT, `lat` TEXT, PRIMARY KEY(`id`));
INSERT INTO room_city SELECT id,name AS city_name, state, country, `coord.lon` AS lon, `coord.lat` AS lat FROM city;
/* To clean up so as to not include the original in the asset
you don't want the asset hold twice the data that it needs to
!!!!!NOTE!!!!! you should use a copy of the original city database
*/
DROP TABLE IF EXISTS city;
ALTER TABLE `room_city` RENAME TO `city`;
VACUUM;
/* Below not needed but good to check all is as expected */
SELECT * FROM city;
SELECT * FROm sqlite_master;
-
请注意,这假定原始数据库与屏幕截图一致。
-
VACUUM 显示后的第一个 SELECT:-
-
-
第二个显示架构:-
-
应该关闭数据库和连接(打开检查并再次关闭以再次检查要复制的文件是否已保存)。
然后将文件复制到资产文件夹中(您可能需要创建它),确保它被命名(可以重命名)city_db.db。 :-
在使用上述演示的情况下,Database 类已更改为现在包含行 .createFromAsset("city_db.db") // <<<<< reinstated
和 MainActivity 更改为:-
public class MainActivity extends AppCompatActivity {
Database db;
DAO dao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Database.getDatabase(this);
dao = db.dao();
for(Entity city: dao.getCitiesByName("%")) { //* <<<<<< changed to show all as only 2 cities in demo
Log.d("CITYINFO-1","CityName is " + city.name + " state/county is " + city.state + " etc.....");
}
for(String cityname: dao.getCityNamesByName("%")) { //* <<<<<< changed to show all as only 2 cities in demo
Log.d("CITYINFO-2","CityName is " + cityname);
}
dao.getCityNamesByName("New%");
}
}
运行时日志显示:-
2021-10-23 15:44:37.480 D/CITYINFO-1: CityName is Santa Rita state/county is etc.....
2021-10-23 15:44:37.480 D/CITYINFO-1: CityName is Santa Elenta state/county is etc.....
2021-10-23 15:44:37.482 D/CITYINFO-2: CityName is Santa Rita
2021-10-23 15:44:37.482 D/CITYINFO-2: CityName is Santa Elenta
所以数据库工作正常。
如果您不转换会怎样?
再次基于屏幕截图,然后应用程序失败:-
2021-10-23 15:50:53.826 6345-6345/a.a.so69683821cities E/AndroidRuntime: FATAL EXCEPTION: main
Process: a.a.so69683821cities, PID: 6345
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so69683821cities/a.a.so69683821cities.MainActivity}: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: city(a.a.so69683821cities.Entity).
Expected:
TableInfo{name='city', columns={country=Column{name='country', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, city_name=Column{name='city_name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lon=Column{name='lon', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, state=Column{name='state', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, lat=Column{name='lat', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='city', columns={country=Column{name='country', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, state=Column{name='state', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, coord.lat=Column{name='coord.lat', type='', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}, coord.lon=Column{name='coord.lon', type='REAL', affinity='4', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
其中 EXPECTED 是 Room 根据 Entity 类期望架构是什么,但 Room 发现资产中的架构不匹配。