Android with Room - How to set a foreign key nullable - android-sqlite

I'm using Room in my Android app. One table (stock_move) contains several Foreign keys. In some case, I need to insert a stock_move without a FK (locationDestId). In this case, SQLite raise an error:
io.reactivex.exceptions.OnErrorNotImplementedException: foreign key constraint failed (code 19)
There is my entity:
#Entity(tableName = "stock_move",
foreignKeys = {
#ForeignKey(
entity = LocationEntity.class,
parentColumns = "id",
childColumns = "location_id",
onDelete = CASCADE
),
#ForeignKey(
entity = LocationEntity.class,
parentColumns = "id",
childColumns = "location_dest_id",
onDelete = CASCADE
),
#ForeignKey(
entity = ProductEntity.class,
parentColumns = "id",
childColumns = "product_id",
onDelete = CASCADE
),
#ForeignKey(
entity = UomEntity.class,
parentColumns = "id",
childColumns = "uom_id",
onDelete = CASCADE
)
}
)
public class StockMoveEntity {
#PrimaryKey(autoGenerate = true)
private int id;
private String name;
#ColumnInfo(name="product_id")
private int mProductId;
#ColumnInfo(name="uom_id")
private int mUomId;
#ColumnInfo(name="location_id")
private int mLocationId;
#ColumnInfo(name="location_dest_id")
private int mLocationDestId;
private int mProductQty;
public StockMoveEntity(int id, String name, int productId, int uomId, int locationId, int locationDestId, int productQty) {
this.id = id;
this.name = name;
mProductId = productId;
mUomId = uomId;
mLocationId = locationId;
mLocationDestId = locationDestId;
mProductQty = productQty;
}
}

If you change the data type for the mLocationDestId variable from int to Integer the schema should be generated without the NOT NULL constraint for your location_dest_id column since Integer is nullable.
I've only tested this with version 1.1.0-alpha3 of room so I don't know if it works with 1.0.0 as well.

Related

Hibernate: getting entity from PostgreSQL view

Let's imagine that we have two entities in the database. We have a simple Server with a unique Id:
PostgreSQL:
CREATE TABLE public.servers (
id bigint NOT NULL,
name character varying(64) NOT NULL
);
ALTER TABLE ONLY public.servers
ADD CONSTRAINT servers_pkey PRIMARY KEY (id);
Class entity:
#Entity
#Table(name = "servers")
public class Server {
#Id
private long id;
private String name;
#OneToMany(mappedBy = "server", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<Channel> channels;
public Server() {}
public Server(String name) {
this.name = name;
channels = new ArrayList<>();
}
// Getters and Setters...
}
Each Server can have several Channels that also have a unique id and belong to a server:
PostgreSQL:
CREATE TABLE public.channels (
id bigint NOT NULL,
server_id bigint NOT NULL,
name character varying NOT NULL
);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT channels_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.channels
ADD CONSTRAINT fkey_channel_server FOREIGN KEY (server_id) REFERENCES public.servers(id);
Class entity:
#Entity
#Table (name = "channels")
public class Channel {
#Id
private long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "server_sn")
private Server server;
public Channel() {}
public Channel(Server server, String name) {
this.server = server;
this.name = name;
}
// Getters and Setters...
}
Next, I create a View in PostgreSQL:
CREATE VIEW public.summary AS
SELECT servers.s_snowflake AS server_id, channels.c_snowflake AS channel_id, channels.name FROM servers
JOIN channels ON servers.s_snowflake = channels.server_sn;
I want to get data from this view but I'm facing a problem on how to implement the class. I have tried something like this:
#Embeddable
class SummaryPK implements Serializable {
private long server_id;
private long channel_id;
}
#Entity
#Immutable
#Subselect("SELECT * FROM summary")
public class Summary {
#EmbeddedId
private SummaryPK summaryPK;
#MapsId("server_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Server.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Server server;
#MapsId("channel_id")
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Channel.class)
#JoinColumn(name = "id", insertable = false, updatable = false)
private Channel channel;
private String name;
// Getters and Setters
}
In the program, I want to receive a list of Entity, for example, by passing the Server ID. Any idea how to implement a working structure here?
Fixed the problem, there were incorrect id in JoinColumn:
#JoinColumn(name = "server_id", insertable = false, updatable = false)
#JoinColumn(name = "channel_id", insertable = false, updatable = false)

Generated Key looks wrong when ManyToOne and OneToMany

I have two entities with a relationship, UserEntity:
#Embeddable
public class UserId extends EntityId implements Serializable {
#Column( length = CCEntity.ID_MAX_SIZE, name = ccIdCN )
private String ccId;
#Column( length = NAME_MAX_SIZE, name = userIdCN )
private String userId;
...
}
#Entity
#Table(name = TableNames.CC_Users)
public class UserEntity {
#EmbeddedId
private UserId id;
...
#OneToMany(targetEntity = ProfileEntity.class, mappedBy = "user", fetch = FetchType.EAGER, cascade = { CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE })
private List<ProfileEntity> profiles;
And the ProfileEntity:
#Embeddable
public class ProfileId extends EntityId implements Serializable {
#Column( length = CCEntity.ID_MAX_SIZE, name = ccIdCN )
private String ccId;
#Column( length = NAME_MAX_SIZE, name = profileIdCN )
private String profileId;
....
}
#Entity
#Table(name = TableNames.CC_Profile)
public class ProfileEntity {
#EmbeddedId
protected ProfileId id;
...
#ManyToOne
#JoinColumns(value = {
#JoinColumn( nullable = true, name = Columns.referenceIdCN, referencedColumnName = UserId.userIdCN ),
#JoinColumn( nullable = true, name = Columns.ccIdOfReferenceCN, referencedColumnName = UserId.ccIdCN ),
})
private UserEntity user;
When JPA creates the tables it generates the following:
Table CC_USER with primary key: cc_id, user_id. That is correct.
Table CC_PROFILE with primary key: cc_id, user_id, profile_id. Here I don't understand why JPA adds the user_id column as primary key. The table also has the columns: reference_id and cc_id_of_reference_id set as nullable.
I want property user of the ProfileEntity is optional, or nullable. If I try to add an entity with the user as null, I get :
Internal Exception: org.postgresql.util.PSQLException: ERROR: null
value in column "user_id" violates not-null constraint
thanks for any help
Finally I found the issue. I have another Entity with same TableName as ProfileEntity, but a different id. That id contains the user_id column that wasn't expected.

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

JPA entity giving error on implementing Bill of material concept

Trying to implement the Bill of Material concept using JPA entity:-
IDE: Eclipse Helios;
Jars: eclipselink2.4.0 , javax.persistence
Entity is as follows:
#Id
#TableGenerator(name = "Config_Key_Incrementor", table = "id_generator", pkColumnName = "gen_name", valueColumnName = "gen_value", pkColumnValue = "conifg_id_gen", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = TABLE, generator = "Config_Key_Incrementor")
#Column(name = "config_id")
private int configId;
#Column(name = "config_name")
private String configName;
//bi-directional many-to-one association to Bill_Of_Material
#ManyToOne
#PrimaryKeyJoinColumn(name="config_id")
private Configuration parent;
//bi-directional many-to-one association to Bill_Of_Material
#OneToMany(mappedBy="parent")
private List<Configuration> children = new ArrayList<Configuration>();
public Configuration getParent() {
return parent;
}
public void setParent(Configuration parent) {
this.parent = parent;
}
public List<Configuration> getChildren() {
return children;
}
public void setChildren(List<Configuration> children) {
this.children = children;
}
public int getConfigId() {
return configId;
}
public void setConfigId(int configId) {
this.configId = configId;
}
public String getConfigName() {
return configName;
}
public void setConfigName(String configName) {
this.configName = configName;
}
Output:
CREATE TABLE configuration
(
config_id integer NOT NULL,
config_name character varying(255),
CONSTRAINT configuration_pkey PRIMARY KEY (config_id ),
CONSTRAINT fk_configuration_config_id FOREIGN KEY (config_id)
REFERENCES configuration (config_id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
Error:
The table is getting created , but the column parent_config_id is missing and its relation to config_id is also missing.
You are using #PrimaryKeyJoinColumn(name="config_id") which indicates that the primary key is also a foreign key to the referenced Configuration parent - so it is its own parent. You want to use #JoinColumn to define the foreign key, or leave it blank to have it use the default.
#ManyToOne
#JoinColumn(name="parent_config_id", referencedColumnName="config_id")
private Configuration parent;

JPA left join query

Given the following two tables:
CREATE TABLE `x` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name_hash` char(32) NOT NULL,
`access_time` bigint(20) unsigned NOT NULL,
`name` varchar(1024) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_hash` (`name_hash`),
KEY `access_time` (`access_time`),
CONSTRAINT `x_ibfk_1` FOREIGN KEY (`access_time`) REFERENCES `update_time` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `y` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`x` bigint(20) unsigned NOT NULL,
`update_time` bigint(20) unsigned NOT NULL,
`reason` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `x` (`x`,`update_time`),
KEY `reason` (`reason`),
KEY `update_time` (`update_time`),
CONSTRAINT `y_ibfk_1` FOREIGN KEY (`reason`) REFERENCES `reason` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `y_ibfk_2` FOREIGN KEY (`x`) REFERENCES `x` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `y_ibfk_3` FOREIGN KEY (`update_time`) REFERENCES `update_time` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
I used NetBeans to create the following JPA classes (X and Y are not the real names, think I did all of the required changes):
#Entity
#Table(name = "X", catalog = "topiclymobile", schema = "", uniqueConstraints = {
#UniqueConstraint(columnNames = {"name_hash"})})
#NamedQueries({
#NamedQuery(name = "X.findAll", query = "SELECT t FROM X t"),
#NamedQuery(name = "X.findById", query = "SELECT t FROM X t WHERE t.id = :id"),
#NamedQuery(name = "X.findByNameHash", query = "SELECT t FROM X t WHERE t.nameHash = :nameHash"),
#NamedQuery(name = "X.findByName", query = "SELECT t FROM X t WHERE t.name = :name")})
public class X implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id", nullable = false)
private Long id;
#Basic(optional = false)
#Column(name = "name_hash", nullable = false, length = 32)
private String nameHash;
#Basic(optional = false)
#Column(name = "name", nullable = false, length = 1024)
private String name;
#JoinColumn(name = "access_time", referencedColumnName = "id", nullable = false)
#ManyToOne(optional = false)
private UpdateTime accessTime;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "X")
private List<Y> YList;
public X() {
}
public X(Long id) {
this.id = id;
}
public X(Long id, String nameHash, String name) {
this.id = id;
this.nameHash = nameHash;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNameHash() {
return nameHash;
}
public void setNameHash(String nameHash) {
this.nameHash = nameHash;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public UpdateTime getAccessTime() {
return accessTime;
}
public void setAccessTime(UpdateTime accessTime) {
this.accessTime = accessTime;
}
public List<Y> getYList() {
return YList;
}
public void setYList(List<Y> YList) {
this.YList = YList;
}
#Override
public int hashCode() {
int hash = 5;
hash = 89 * hash + (this.nameHash != null ? this.nameHash.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final X other = (X) obj;
if ((this.nameHash == null) ? (other.nameHash != null) : !this.nameHash.equals(other.nameHash)) {
return false;
}
return true;
}
}
#Entity
#Table(name = "Y", catalog = "topiclymobile", schema = "", uniqueConstraints = {
#UniqueConstraint(columnNames = {"X", "update_time"})})
#NamedQueries({
#NamedQuery(name = "Y.findAll", query = "SELECT t FROM Y t"),
#NamedQuery(name = "Y.findById", query = "SELECT t FROM Y t WHERE t.id = :id")})
public class Y implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id", nullable = false)
private Long id;
#JoinColumn(name = "reason", referencedColumnName = "id", nullable = false)
#ManyToOne(optional = false)
private Reason reason;
#JoinColumn(name = "X", referencedColumnName = "id", nullable = false)
#ManyToOne(optional = false)
private X X;
#JoinColumn(name = "update_time", referencedColumnName = "id", nullable = false)
#ManyToOne(optional = false)
private UpdateTime updateTime;
public Y() {
}
public Y(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Reason getReason() {
return reason;
}
public void setReason(Reason reason) {
this.reason = reason;
}
public X getX() {
return X;
}
public void setX(X X) {
this.X = X;
}
public UpdateTime getUpdateTime() {
return updateTime;
}
public void setUpdateTime(UpdateTime updateTime) {
this.updateTime = updateTime;
}
#Override
public int hashCode() {
int hash = 7;
hash = 13 * hash + (this.X != null ? this.X.hashCode() : 0);
hash = 13 * hash + (this.updateTime != null ? this.updateTime.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Y other = (Y) obj;
if (this.X != other.X && (this.X == null || !this.X.equals(other.X))) {
return false;
}
if (this.updateTime != other.updateTime && (this.updateTime == null || !this.updateTime.equals(other.updateTime))) {
return false;
}
return true;
}
}
What I am after is all of the cases that "x" does not have a corresponding "y" for a given time (access_time and update_time are the same thing).
This SQL query works, I just cannot seem to translate it into an JPA query:
SELECT t.id FROM x t LEFT JOIN y r ON t.id = r.x WHERE r.x IS NULL AND t.access_time = 1
It'd be helpful to see your entity classes to construct the actual query, but JPA does support LEFT JOINs. This blog post has a full example, as does this question, but something like
SELECT x FROM X x LEFT JOIN x.y ...
I'm not sure what the rest of the query should be as what you posted does not look like valid SQL (you have WHERE r.x IS NULL, but the schema given defines x on table y as NOT NULL; similarly, having WHERE r.x IS NULL ought to make your left join match nothing, since t.id = r.x would always evaluate to NULL).
EDIT: I'm still confused as to how your sample SQL is a valid query, but something like this seems like it ought to translate into the SQL you provided:
SELECT x FROM X x LEFT JOIN x.yList y where y.x IS NULL and x.accessTime = :accessTime
Where the :accessTime parameter is the value of entityManager.getReference(UpdateTime.class, 1).
Again, though, the FROM x LEFT JOIN y on x.id = y.x WHERE y.x IS NULL should match precisely no rows in Y, whereas (since it's a LEFT JOIN), it will include all the rows in X. In other words, I think your query is equivalent to:
SELECT x.id FROM X where x.access_time = 1
Which would be this in JPA:
SELECT x FROM X x where x.accessTime = :accessTime