When setting DB column to allow NULL in Database the app complains about the Schema - android-room

I am trying to use Room in Android Studio using Kotlin with a pre-packaged database. The database does not set NOT NULL. Using DB SQL Browser it shows that the column has these properties
"Reference" TEXT
There is no NOT NULL. All the other columns in the table do have NOT NULL set.
In the Entity that maps that table I have:
#Entity
data class Meaning (
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "Id") val id: Int,
#NonNull #ColumnInfo(name = "Contents") val contents: String,
/*
* It's OK for Reference to be Null
*/
#ColumnInfo(name = "Reference") val reference: String,
#NonNull #ColumnInfo(name = "SymbolId") val symbolId: Int,
#NonNull #ColumnInfo(name = "Local") val local: Int
)
It builds and installs, but fails when running with this error:
java.lang.IllegalStateException: Pre-packaged database has an invalid schema: Meaning(<stuff>.Meaning).
Expected:
TableInfo{name='Meaning', <unimportant columns>, Reference=Column{name='Reference', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'},<more unimportant columns>}
Found:
TableInfo{name='Meaning', <unimportant columns>, Reference=Column{name='Reference', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, <more unimportant columns}
Note that the Found has notNull=false, which seems correct because NOT NULL is not specified in the database.
The Expected has notNull=true even though in the Entity, #NonNull was not specified for the Reference column.
So, I am confused why the Entity is expecting Reference column to be notNull=true.
Any pointers are welcome.

#ColumnInfo(name = "Reference") val reference: String?,
would equate to notNull = false.
i.e. the ? indicates null allowed.
So, I am confused why the Entity is expecting Reference column to be notNull=true.
Because the annotation processing sees String which cannot be null. Only if it sees String? then can the value be nullable.
That is if you you use:-
#Entity
data class Meaning (
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "Id") val id: Int,
#NonNull #ColumnInfo(name = "Contents") val contents: String,
/*
* It's OK for Reference to be Null
*/
#ColumnInfo(name = "Reference") val reference: String?,
#NonNull #ColumnInfo(name = "SymbolId") val symbolId: Int,
#NonNull #ColumnInfo(name = "Local") val local: Int
)
and then compile, the generated Java (expected) for the #Database annotated class suffixed with _Impl includes:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `Meaning` (`Id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `Contents` TEXT NOT NULL, `Reference` TEXT, `SymbolId` INTEGER NOT NULL, `Local` INTEGER NOT NULL)");
i.e.
, `Reference` TEXT,
However without the ? then :-
_db.execSQL("CREATE TABLE IF NOT EXISTS `Meaning` (`Id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `Contents` TEXT NOT NULL, `Reference` TEXT NOT NULL, `SymbolId` INTEGER NOT NULL, `Local` INTEGER NOT NULL)");
i.e.
`Reference` TEXT NOT NULL,

Related

#OneToMany field is wrongly included in JPA query

I have a small project to tinker with Spring, where I have two entities with a one to many association: 1 Restaurant -> N Dishes.
I have the following PostgreSQL schema for that:
create table if not exists restaurants (
restaurant_id uuid primary key,
name varchar(512) not null,
description varchar(1024) not null,
address varchar(512) not null,
photo_url varchar(1024)
);
create table if not exists dishes (
dish_id uuid primary key,
name varchar(512) not null,
description varchar(1024),
photo_url varchar(1024),
restaurant_id uuid references restaurants(restaurant_id) not null,
price int not null check (price > 0)
);
With the following JPA Entities:
#Entity
#Table(name = "restaurants")
class Restaurants(
#Id
var restaurantId: UUID,
var name: String,
var description: String,
var photoUrl: String?,
) {
#OneToMany(mappedBy = "restaurant")
#JoinColumn(name = "restaurant_id", nullable = false)
var dishes: MutableList<Dishes> = mutableListOf()
}
#Entity
#Table(name = "dishes")
class Dishes(
#Id
var dishId: UUID,
var name: String,
var description: String,
var photoUrl: String?,
var price: Int,
#ManyToOne(optional = false)
#JoinColumn(name = "restaurant_id", nullable = false)
var restaurant: Restaurants
)
I have defined a RestaurantsRepository as follows:
interface RestaurantsRepository: R2dbcRepository<Restaurants, UUID> {
fun findByRestaurantId(restaurantId: UUID): Mono<Restaurants>
}
The problem I'm having is that when I call findByRestaurantId I have the following exception:
org.springframework.r2dbc.BadSqlGrammarException: executeMany; bad SQL grammar [SELECT restaurants.restaurant_id, restaurants.name, restaurants.description, restaurants.photo_url, restaurants.dishes FROM restaurants WHERE restaurants.restaurant_id = $1]; nested exception is io.r2dbc.postgresql.ExceptionFactory$PostgresqlBadGrammarException: [42703] column restaurants.dishes does not exist
at org.springframework.r2dbc.connection.ConnectionFactoryUtils.convertR2dbcException(ConnectionFactoryUtils.java:235) ~[spring-r2dbc-5.3.21.jar:5.3.21]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Why is the #OneToMany field included in the SQL query?
You are trying to use Spring Data R2DBC (R2dbcRepository) in conjunction with JPA annotations. It won't work: these are two different technologies. R2DBC does not support #ManyToOne nor #JoinColumn so the annotations are simply ignored.

Insert data and get back id in mybatis

I should save the record in the database and get the record id in the response. After a long search and research I came up with the following option.
data class User(
val id: UUID? = null,
val username: String,
...
)
UserRepo:
#Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
#Insert("""
INSERT INTO "user" (
username,
...
) VALUES (
#{username},
...
)
""")
fun save(user: User): User
in response I get the following.
org.apache.ibatis.binding.BindingException: Mapper method '...UserRepository.save' has an unsupported return type: class ...entity.User
in the following case, I don't get an error, but I don't get an id either, how can I do it correctly? Used Select instead of Insert
#Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
#Select("""
INSERT INTO "user" (
username
) VALUES (
#{username}
)
""")
fun save(user: User): User
if we solve my first question, then I would like to know if it is possible to get the answer not in the User class, but in another one, for example, UserResponse? That is, send a request to the User class, and receive a response in the UserResponse
UPDATE
I was able to get the id after changing the id type to String.
Like this:
data class User(
val id: String? = null,
val username: String,
...
)
Apparently some settings are needed for UUID?
Has anyone faced such a problem?

Android room data relations

I have multiple object tyes inside a parent class.Say I have a College class as below
data class College(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
val groups: List<Group>? = null,
val status: String? = null,
)
data class Group(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
val students: List<Students>? = null,
)
The problem here is there is not relational id in child tables.I mean JSON received don't give relation of groups inside college with collegeId in Groups table or relation of students in group table.
The JSON received is as below
"college": [
{
"id": "collegeid",
"groups": [
"id": "groupid"
"name": "BCOM" // Here no collegeId is mentioned inside it
]
}
If is use #Embedded keyword it is throwing "Entities and POJOs must have a usable public constructor".
Is there anyway with above JSON I can set the id of college inside group and use it as foreign key for relations.
I have used Typeconverters and is working fine but now I need to create relations between these tables with above type of JSON.
I use Gson parsing
I believe that you may be getting mixed up with how to utilise relationships/tables.
IF you have
#Entity
data class College(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
val groups: List<Group>? = null,
val status: String? = null,
)
this will be a table where the groups column will (in theory) contain a List of Groups, there is no relationship as there is just a column with a single stream of data (probably as JSON string).
However, if you wanted to have a table with the Colleges, a table with the Groups and a table with the Students then you wouldn't embed the Groups within the College and subsequently the Students within the Groups.
Rather, if you want to approach this from a db relationship aspect. Assuming that a Group MUST belong to one and only one College and that a Student MUST only belong to a single Group then you would have something along the lines of:-
#Entity
data class College(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
//val groups: List<Group>? = null, //<<<<< NO NEED (see Group)
val status: String? = null,
)
#Entity
data class Group(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
//val students: List<Students>? = null, //<<<<< NO NEED (similar to Group)
val collegeId: String //<<<<< ADDED for relationships to parent College
)
similar with Students
the crucial factor here is the new column/field/variable collegeId, which should contain the id of the parent college.
To get the College(s) with the related groups then you can have a class that has #Embedded annotation for the parent (the College) and #Relation annotation for the children (Groups). e.g.
data class CollegeWithRelatedGroups(
#Embedded
val college: College,
#Relation(
entity = Group::class,
parentColumn = "id",
entityColumn = "collegeId"
)
val groups: List<Group>
)
Working Example
Here's a working example that uses the above and adds (inserts) 3 Colleges, and then 6 Groups. The first College has 3 related Groups, the second 2 and the third 1.
The example then extracts the Colleges with the related Groups outputting the result to the log.
College and also CollegeWithRelatedGroups
#Entity
data class College(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
//val groups: List<Group>? = null,
val status: String? = null
)
data class CollegeWithRelatedGroups(
#Embedded
val college: College,
#Relation(
entity = Group::class,
parentColumn = "id",
entityColumn = "collegeId"
)
val groups: List<Group>
)
Group (with Foreign Key constraint)
#Entity(
/* Optional but suggested Foreign Key to enforce referential integrity
*/
foreignKeys = [
ForeignKey(
entity = College::class,
parentColumns = ["id"],
childColumns = ["collegeId"],
/* Optional within Foreign Key - helps maintain referential integrity (if CASCADE)
ON DELETE will delete the Groups that are related to a College if the College is deleted
ON UPDATE will change the collegeId of Groups that are related to a College if the id of the College is changed
*/
onDelete = ForeignKey.CASCADE,
onUpdate = ForeignKey.CASCADE
)
]
)
data class Group(
#PrimaryKey
val id:String,
val name: String? = null,
val description: String? = null,
//val students: List<Student>? = null,
#ColumnInfo(index = true) /* faster to access via relationship if indexed */
val collegeId: String
)
AllDao
#Dao
interface AllDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(college: College): Long
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(group: Group): Long
#Transaction
#Query("SELECT * FROM college")
fun getCollegesWithRelatedGroups(): List<CollegeWithRelatedGroups>
}
TheDatabase (note for brevity and convenience allows main thread processing)
#Database(entities = [College::class, Group::class], version = 1, exportSchema = false)
abstract class TheDatabase : RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
#Volatile
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, TheDatabase::class.java, "the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
Note there is no need for TypeConverters as objects are not being stored (aka simple types are stored)
Activity Code (MainActivity in this case)
const val TAG = "DBINFO"
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
dao.insert(College("College1","The first College","Opened"))
dao.insert(College("College2","The second College","Opened"))
dao.insert(College("New College","A new College","Being built"))
dao.insert(Group("GroupA","Group A or something","The first Group - hence A","College1",))
dao.insert(Group("GroupB","Group B or whatever"," The second Group ...", "College2"))
dao.insert(Group("GroupC","Group C on so on","The third Group ...","New College"))
dao.insert(Group("GroupX","Group X ...","The Xth group","College1"))
dao.insert(Group("GroupY","...","...","College1"))
dao.insert(Group("GroupZ","...","...","College2"))
for (cwrg in dao.getCollegesWithRelatedGroups()) {
Log.d(TAG,"College is ID=${cwrg.college.id} Desc=${cwrg.college.description} Name=${cwrg.college.name} Status=${cwrg.college.name}\nIt has ${cwrg.groups.size} Groups. They are:-")
for (g in cwrg.groups) {
Log.d(TAG,"\n\t\tGroup is ID=${g.id} Name=${g.name} Desc=${g.description} it reference CollegeID=${g.collegeId}")
}
}
}
}
Note The status has not been supplied for the Colleges. so they will be null.
RESULT the log includes :-
D/DBINFO: College is ID=College1 Desc=Opened Name=The first College Status=The first College
It has 3 Groups. They are:-
D/DBINFO: Group is ID=GroupA Name=Group A or something Desc=The first Group - hence A it reference CollegeID=College1
D/DBINFO: Group is ID=GroupX Name=Group X ... Desc=The Xth group it reference CollegeID=College1
D/DBINFO: Group is ID=GroupY Name=... Desc=... it reference CollegeID=College1
D/DBINFO: College is ID=College2 Desc=Opened Name=The second College Status=The second College
It has 2 Groups. They are:-
D/DBINFO: Group is ID=GroupB Name=Group B or whatever Desc= The second Group ... it reference CollegeID=College2
D/DBINFO: Group is ID=GroupZ Name=... Desc=... it reference CollegeID=College2
D/DBINFO: College is ID=New College Desc=Being built Name=A new College Status=A new College
It has 1 Groups. They are:-
D/DBINFO: Group is ID=GroupC Name=Group C on so on Desc=The third Group ... it reference CollegeID=New College
The Database via App Inspection :-
and

Is it possible to Use Snowflake with Spring Boot / JPA / Hibernate

I am creating a service which writes directly to a snowflake database.
I am having a lot of trouble trying to get spring data jpa to work effectively with Snowflake. My main issue is that I am unable to save an entity to the Snowflake DB through Jpa Repository interface Save method. Because this application is being used to dump data into Snowflake, being able to leverage JPA would make life a lot easier.
I would prefer not to have to roll my own native queries so my question is whether it's possible to leverage Hibernate when working with Snowflake.
The main thing I want to be able to do is persist entities using the Jpa Repositories inbuild Save method.
Below is my current configuration. Any ideas on what could be improved in the configuration to get this working would be appreciated, or also any opinion on whether it is possible or not.
spring:
profiles:
active: local
application:
name: Service
datasource:
driverClassName: net.snowflake.client.jdbc.SnowflakeDriver
url: ${SPRING_DATASOURCE_URL}
username: ${SPRING_DATASOURCE_USERNAME}
password: ${SPRING_DATASOURCE_PASSWORD}
flyway:
locations: classpath:db/migration/common,classpath:db/migration/snowflake
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.SQLServerDialect
order_inserts: true
create sequence award_event_id_seq;
create table award_event
(
id INT NOT NULL DEFAULT award_event_id_seq.nextval PRIMARY KEY,
event_source_system varchar not null,
event_trigger VARCHAR NOT NULL,
event_triggered_by VARCHAR NOT NULL,
event_timestamp TIMESTAMP NOT NULL
)
#Entity(name = "award_event")
#SequenceGenerator(name = "award_event_id_seq", sequenceName = "award_event_id_seq", allocationSize = 1)
data class AwardEvent(
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Int = -1,
val eventTrigger: String,
val eventTriggeredBy: String,
val eventTimestamp: LocalDateTime,
val eventSourceSystem: String
)
override fun receiveMessage(message: String) {
logger.info("Receiving award event: $message")
val awardEvent: AwardEventMessage = message.toObject()
// This Save method does not work and throws an error specified below
awardEventRepository.save(awardEvent.toAwardEvent())
}
2021-01-08 10:49:28.163 ERROR 3239 --- [nio-9106-exec-1] o.hibernate.id.enhanced.TableStructure : could not read a hi value
net.snowflake.client.jdbc.SnowflakeSQLException: SQL compilation error:
syntax error line 1 at position 50 unexpected 'with'.
syntax error line 1 at position 72 unexpected ')'.
at net.snowflake.client.jdbc.SnowflakeUtil.checkErrorAndThrowExceptionSub(SnowflakeUtil.java:124)
at net.snowflake.client.jdbc.SnowflakeUtil.checkErrorAndThrowException(SnowflakeUtil.java:64)
at net.snowflake.client.core.StmtUtil.pollForOutput(StmtUtil.java:434)
at net.snowflake.client.core.StmtUtil.execute(StmtUtil.java:338)
at net.snowflake.client.core.SFStatement.executeHelper(SFStatement.java:506)
at net.snowflake.client.core.SFStatement.executeQueryInternal(SFStatement.java:233)
at net.snowflake.client.core.SFStatement.executeQuery(SFStatement.java:171)
at net.snowflake.client.core.SFStatement.execute(SFStatement.java:754)
at net.snowflake.client.jdbc.SnowflakeStatementV1.executeQueryInternal(SnowflakeStatementV1.java:245)
at net.snowflake.client.jdbc.SnowflakePreparedStatementV1.executeQuery(SnowflakePreparedStatementV1.java:117)
Just as a follow up, I was unable to get the application up and running using the approach I outlined above. I am still unsure why but think it may have been to do with a lack of support for snowflake sequences as the generation type for the primary key in spring.
I changed the generation type to UUID and the application started to work as expected in turn. There was no requirements for what type of primary key was needed so this approach was satisfactory.
create sequence award_event_id_seq;
create table award_event
(
id varchar not null constraint award_event_pkey primary key,
event_source_system varchar not null,
event_trigger varchar not null,
event_triggered_by varchar not null,
event_timestamp timestamp not null
)
#Entity(name = "award_event")
data class AwardEvent(
#Id
#GeneratedValue
#Type(type = "uuid-char")
val id: UUID = UUID.randomUUID(),
val eventTrigger: String,
val eventTriggeredBy: String,
val eventTimestamp: LocalDateTime,
val eventSourceSystem: String
)

Dealing with nullable UUID in a controller - SpringBoot and Postgres

I have an entity that includes a nullable UUID (but it is not an ID), as follows:
#Column(nullable = true)
var myField: UUID? = null
I also have a dto converter as follows:
myField = entity.myField.let { it }
I can correctly store a null value for the UUID field, but when inspecting the GET response from my controller whenever I pass a null value for such field the body does not contain the myField at all. What is the reason for that? How does SpringBoot deal with null value for UUID.
Thanks

Resources