我可以在 Room 中添加检查约束吗?
Can I add check constraints in Room?
在我的数据库中,我有 3 个 table:problems
、users
和 roles
。 roles
table 有两个值:client 和 coach。
数据库规则:
- 一个用户只有一个角色(客户或教练)。
- 一个问题有一位教练和一位客户。
problems
实体有两个指向 users
table 的外键:coach_id 和 client_id。我可以使用 Android Room 添加检查约束以确保问题符合此规则吗?
我想避免一个问题有两个客户或两个教练的情况。
数据库概念图
目前ROOM不支持添加CHECK约束。
但是,您可以通过删除 table 然后创建 table 来引入 CHECK 约束(可能不可取,因为这可能会引入持续的并发症,例如在迁移时)。
但是,CHECK 约束是有限的,不能包括子查询,我认为这会使事情复杂化,甚至可能排除 suitable/usable CHECK 约束。
- 我相信您会看到类似
coach INTEGER CHECK((SELECT authority FROM user JOIN role ON role.roleid = user.role) = 0) REFERENCES user(userid) ON DELETE CASCADE ON UPDATE CASCADE,
的内容(假设,由于不清楚哪一列表示角色类型,教练必须拥有权限为 0 的角色)。但是,不能使用 CHECK,因为它会导致 subqueries prohibited in CHECK constraints 错误。
因此,在插入问题的过程中以编程方式进行此类检查可能会更简单。也就是说,对于每种类型 (coach/client),如果指定了不正确的角色,则从用户那里获取角色并拒绝实际插入到数据库中。这更类似于 Room 的 OO 立场(即 tables 支持并根据对象创建)。
例子
或许根据你的问题考虑以下几点:-
Role.java(角色Table)
@Entity(tableName = "role")
public class Role {
public static final int AUTHORITY_COACH = 0;
public static final int AUTHORITY_CLIENT = 1;
@PrimaryKey
Long roleid;
String description;
int authority;
public Role(String description, int authority) {
this.description = description;
if ( authority >= AUTHORITY_CLIENT) {
this.authority = AUTHORITY_CLIENT;
} else {
this.authority = AUTHORITY_COACH;
}
}
public Long getRoleid() {
return roleid;
}
public void setRoleid(Long roleid) {
this.roleid = roleid;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getAuthority() {
return authority;
}
public void setAuthority(int authority) {
this.authority = authority;
}
}
User.java(用户 table)
@Entity(
tableName = "user",
foreignKeys = {
@ForeignKey(
entity = Role.class,
parentColumns = {"roleid"},
childColumns = {"role"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index("role")
}
)
public class User {
@PrimaryKey
Long userid;
String name;
String surname;
String username;
String email;
String password;
long role;
public User(String name, String surname, String username, String email, String password, long role) {
this.name = name;
this.surname = surname;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
public Long getUserid() {
return userid;
}
public void setUserid(Long userid) {
this.userid = userid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRole() {
return role;
}
public void setRole(long role) {
this.role = role;
}
}
Problem.java(问题Table)
@Entity(
tableName = "problem",
foreignKeys = {
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"coach"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"client"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index(value = "coach"),
@Index(value = "client")
}
)
public class Problem {
@PrimaryKey
Long problemid;
String title;
String status;
long coach;
long client;
public Problem(){}
public Problem(String title, String status, User coach, User client) {
this.title = title;
this.status = status;
this.coach = coach.getUserid();
this.client = client.getUserid();
}
public Long getProblemid() {
return problemid;
}
public void setProblemid(Long problemid) {
this.problemid = problemid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public long getCoach() {
return coach;
}
public void setCoach(long coach) {
this.coach = coach;
}
public long getClient() {
return client;
}
public void setClient(long client) {
this.client = client;
}
}
UserWithRole.java(POJO 将 User 与 User 的角色结合起来)
public class UserWithRole {
@Embedded
User user;
@Relation(entity = Role.class,entityColumn = "roleid",parentColumn = "role")
Role userrole;
public UserWithRole(){}
public User getUser() {
return user;
}
public Role getUserrole() {
return userrole;
}
}
- 未实际使用(但可用于检查)
AllDao.java(数据访问全部合并)
@Dao
public interface AllDao {
@Insert
long insertRole(Role role);
@Insert
long insertUser(User user);
@Insert
long insertProblem(Problem problem);
@Query("SELECT (authority = " + Role.AUTHORITY_COACH + ") FROM user JOIN role ON role.roleid = user.role WHERE userid = :userid ")
boolean isUserACoach(long userid);
@Query("SELECT * FROM user WHERE userid = :userid")
User getUserById(long userid);
}
- isUuserACoach 是复制上面假设的 CHECK 约束的重要查询。
Database.java(房间@Database)
@androidx.room.Database(version = 1,entities = {Role.class,User.class,Problem.class})
public abstract class Database extends RoomDatabase {
abstract AllDao allDao();
}
MainActivity.java(全部测试)
public class MainActivity extends AppCompatActivity {
Database database;
AllDao allDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = Room.databaseBuilder(
this,
Database.class,
"mydb"
)
.allowMainThreadQueries()
.build();
allDao = database.allDao();
long coachRole = allDao.insertRole(new Role("Coach",Role.AUTHORITY_COACH));
long newClientRole = allDao.insertRole(new Role("New Client",Role.AUTHORITY_CLIENT));
long oldClientRole = allDao.insertRole(new Role("Old Client",Role.AUTHORITY_CLIENT));
long fredBloggs = allDao.insertUser(new User("Fred","Bloggs","fblog","fblog@mail.com","password",coachRole));
long marySmith = allDao.insertUser(new User("Mary","Smith","msmith","msmith@mail.com","password",newClientRole));
long anneWalker = allDao.insertUser(new User("Anne","Walker","awalker","awalkjer@mail.com","password",oldClientRole));
//<<<<<<<<<< SHOULD BE OK TO ADD >>>>>>>>>>
if (verifyAndAddProblem("Problem 1","new",allDao.getUserById(fredBloggs),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (annWalker is NOT a coach) >>>>>>>>>>
if (verifyAndAddProblem("Problem 2","new",allDao.getUserById(anneWalker),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (fredBloggs is a coach BUT is NOT a client) >>>>>>>>>>
if (verifyAndAddProblem("Problem 3","new",allDao.getUserById(fredBloggs),allDao.getUserById(fredBloggs)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
}
//ALTERNATIVE TO CHECK CONSTRAINT DONE HERE
private long verifyAndAddProblem(String title, String status, User coach, User client) {
long rv = -1;
if (!allDao.isUserACoach(coach.getUserid()) || allDao.isUserACoach(client.getUserid())) return rv;
return allDao.insertProblem(new Problem(title,status,coach,client));
}
}
当 运行 日志包含:-
2020-01-02 08:47:33.647 D/PROBLEMADDRESULT: Problem Added
2020-01-02 08:47:33.658 D/PROBLEMADDRESULT: Problem not added
2020-01-02 08:47:33.661 D/PROBLEMADDRESULT: Problem not added
在我的数据库中,我有 3 个 table:problems
、users
和 roles
。 roles
table 有两个值:client 和 coach。
数据库规则:
- 一个用户只有一个角色(客户或教练)。
- 一个问题有一位教练和一位客户。
problems
实体有两个指向 users
table 的外键:coach_id 和 client_id。我可以使用 Android Room 添加检查约束以确保问题符合此规则吗?
我想避免一个问题有两个客户或两个教练的情况。
数据库概念图
目前ROOM不支持添加CHECK约束。
但是,您可以通过删除 table 然后创建 table 来引入 CHECK 约束(可能不可取,因为这可能会引入持续的并发症,例如在迁移时)。
但是,CHECK 约束是有限的,不能包括子查询,我认为这会使事情复杂化,甚至可能排除 suitable/usable CHECK 约束。
- 我相信您会看到类似
coach INTEGER CHECK((SELECT authority FROM user JOIN role ON role.roleid = user.role) = 0) REFERENCES user(userid) ON DELETE CASCADE ON UPDATE CASCADE,
的内容(假设,由于不清楚哪一列表示角色类型,教练必须拥有权限为 0 的角色)。但是,不能使用 CHECK,因为它会导致 subqueries prohibited in CHECK constraints 错误。
- 我相信您会看到类似
因此,在插入问题的过程中以编程方式进行此类检查可能会更简单。也就是说,对于每种类型 (coach/client),如果指定了不正确的角色,则从用户那里获取角色并拒绝实际插入到数据库中。这更类似于 Room 的 OO 立场(即 tables 支持并根据对象创建)。
例子
或许根据你的问题考虑以下几点:-
Role.java(角色Table)
@Entity(tableName = "role")
public class Role {
public static final int AUTHORITY_COACH = 0;
public static final int AUTHORITY_CLIENT = 1;
@PrimaryKey
Long roleid;
String description;
int authority;
public Role(String description, int authority) {
this.description = description;
if ( authority >= AUTHORITY_CLIENT) {
this.authority = AUTHORITY_CLIENT;
} else {
this.authority = AUTHORITY_COACH;
}
}
public Long getRoleid() {
return roleid;
}
public void setRoleid(Long roleid) {
this.roleid = roleid;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public int getAuthority() {
return authority;
}
public void setAuthority(int authority) {
this.authority = authority;
}
}
User.java(用户 table)
@Entity(
tableName = "user",
foreignKeys = {
@ForeignKey(
entity = Role.class,
parentColumns = {"roleid"},
childColumns = {"role"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index("role")
}
)
public class User {
@PrimaryKey
Long userid;
String name;
String surname;
String username;
String email;
String password;
long role;
public User(String name, String surname, String username, String email, String password, long role) {
this.name = name;
this.surname = surname;
this.username = username;
this.email = email;
this.password = password;
this.role = role;
}
public Long getUserid() {
return userid;
}
public void setUserid(Long userid) {
this.userid = userid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRole() {
return role;
}
public void setRole(long role) {
this.role = role;
}
}
Problem.java(问题Table)
@Entity(
tableName = "problem",
foreignKeys = {
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"coach"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
),
@ForeignKey(
entity = User.class,
parentColumns = {"userid"},
childColumns = {"client"},
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
},
indices = {
@Index(value = "coach"),
@Index(value = "client")
}
)
public class Problem {
@PrimaryKey
Long problemid;
String title;
String status;
long coach;
long client;
public Problem(){}
public Problem(String title, String status, User coach, User client) {
this.title = title;
this.status = status;
this.coach = coach.getUserid();
this.client = client.getUserid();
}
public Long getProblemid() {
return problemid;
}
public void setProblemid(Long problemid) {
this.problemid = problemid;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public long getCoach() {
return coach;
}
public void setCoach(long coach) {
this.coach = coach;
}
public long getClient() {
return client;
}
public void setClient(long client) {
this.client = client;
}
}
UserWithRole.java(POJO 将 User 与 User 的角色结合起来)
public class UserWithRole {
@Embedded
User user;
@Relation(entity = Role.class,entityColumn = "roleid",parentColumn = "role")
Role userrole;
public UserWithRole(){}
public User getUser() {
return user;
}
public Role getUserrole() {
return userrole;
}
}
- 未实际使用(但可用于检查)
AllDao.java(数据访问全部合并)
@Dao
public interface AllDao {
@Insert
long insertRole(Role role);
@Insert
long insertUser(User user);
@Insert
long insertProblem(Problem problem);
@Query("SELECT (authority = " + Role.AUTHORITY_COACH + ") FROM user JOIN role ON role.roleid = user.role WHERE userid = :userid ")
boolean isUserACoach(long userid);
@Query("SELECT * FROM user WHERE userid = :userid")
User getUserById(long userid);
}
- isUuserACoach 是复制上面假设的 CHECK 约束的重要查询。
Database.java(房间@Database)
@androidx.room.Database(version = 1,entities = {Role.class,User.class,Problem.class})
public abstract class Database extends RoomDatabase {
abstract AllDao allDao();
}
MainActivity.java(全部测试)
public class MainActivity extends AppCompatActivity {
Database database;
AllDao allDao;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = Room.databaseBuilder(
this,
Database.class,
"mydb"
)
.allowMainThreadQueries()
.build();
allDao = database.allDao();
long coachRole = allDao.insertRole(new Role("Coach",Role.AUTHORITY_COACH));
long newClientRole = allDao.insertRole(new Role("New Client",Role.AUTHORITY_CLIENT));
long oldClientRole = allDao.insertRole(new Role("Old Client",Role.AUTHORITY_CLIENT));
long fredBloggs = allDao.insertUser(new User("Fred","Bloggs","fblog","fblog@mail.com","password",coachRole));
long marySmith = allDao.insertUser(new User("Mary","Smith","msmith","msmith@mail.com","password",newClientRole));
long anneWalker = allDao.insertUser(new User("Anne","Walker","awalker","awalkjer@mail.com","password",oldClientRole));
//<<<<<<<<<< SHOULD BE OK TO ADD >>>>>>>>>>
if (verifyAndAddProblem("Problem 1","new",allDao.getUserById(fredBloggs),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (annWalker is NOT a coach) >>>>>>>>>>
if (verifyAndAddProblem("Problem 2","new",allDao.getUserById(anneWalker),allDao.getUserById(marySmith)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
//<<<<<<<<<< SHOULD NOT BE ADDED (fredBloggs is a coach BUT is NOT a client) >>>>>>>>>>
if (verifyAndAddProblem("Problem 3","new",allDao.getUserById(fredBloggs),allDao.getUserById(fredBloggs)) > 0) {
Log.d("PROBLEMADDRESULT","Problem Added");
} else {
Log.d("PROBLEMADDRESULT","Problem not added");
}
}
//ALTERNATIVE TO CHECK CONSTRAINT DONE HERE
private long verifyAndAddProblem(String title, String status, User coach, User client) {
long rv = -1;
if (!allDao.isUserACoach(coach.getUserid()) || allDao.isUserACoach(client.getUserid())) return rv;
return allDao.insertProblem(new Problem(title,status,coach,client));
}
}
当 运行 日志包含:-
2020-01-02 08:47:33.647 D/PROBLEMADDRESULT: Problem Added
2020-01-02 08:47:33.658 D/PROBLEMADDRESULT: Problem not added
2020-01-02 08:47:33.661 D/PROBLEMADDRESULT: Problem not added