I have a Grails application that will run against either a SQL Server or Oracle backend. I am using GORM as an ORM.
I want to map a large text field in a way that supports both database types. In my Grails domain class I have something like:
class Note {
String content
static constraints = {
content nullable: false, blank: false
}
}
I then declare database tables that look like this:
-- oracle
CREATE TABLE NOTE
(
id NUMBER(19, 0) NOT NULL,
version NUMBER(19, 0) NOT NULL,
content CLOB NOT NULL
);
-- SQL Server
CREATE TABLE NOTE
(
id NUMERIC(19, 0) NOT NULL,
version NUMERIC(19, 0) NOT NULL,
content NVARCHAR(MAX) NOT NULL
);
GORM is running in validate mode on startup, and I can't find a combination of Oracle and SQL Server data types and GORM mappings that allow the storage or large text fields without GORM failing to start correctly.
I have tried:
setting the type to text in mappings, but this doesn't seem to work. Oracle complains about expecting the content field to be of type long, and SQL Server wants a type of text in these circumstances.
setting the type to clob, which passes schema validation but then doesn't allow me to set the field as a string value - GORM expects data of type CLOB.
How should I configure my database definitions and GORM to make this work?
As hackish as it is, a solution eventually emerged: by querying the Grails configuration at startup time, you can select an appropriate data type.
class Note {
String content
static constraints = {
content nullable: false, blank: false
}
static mappings = {
content sqlType: DbSupport.bigStringType
}
}
class DbSupport {
static def getBigStringType() {
// examine which hibernate dialect is selected, and pick
// an appropriate type mapping for that database type:
def dialect = ApplicationHolder.application.config.dataSource.dialect
switch (dialect) {
case "org.hibernate.dialect.SQLServerDialect":
return "nvarchar"
break
case "org.hibernate.dialect.Oracle10gDialect":
return "clob"
break
}
}
}
Related
Another headache :-( Someone please help. I'm simply trying to batch create domain instances using eachWithIndex within my bootstrap file in my Grails 3 project.
Here's my domain class...
package ttt_server
class TttPriority {
String name
int order
Date dateCreated
static constraints = {
name blank: false, nullable: false
order blank: false, nullable: false
dateCreated nullable:true, blank:true
}
}
Here's my bootstrap code for batch creation...
["Mortal","Major","Critical","Minor","Nice To Have"].eachWithIndex{ name, idx ->
new TttPriority(name: name, order: idx).save(flush:true)
}
I'm near to the point of pulling my hair out on this one.
Here's the error....
2017-07-31 16:30:41.755 ERROR --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order) values (0, 'Mortal', 0)' at line 1
2017-07-31 16:30:41.817 ERROR --- [ main] o.s.boot.SpringApplication >: Application startup failed
org.springframework.jdbc.BadSqlGrammarException: Hibernate operation: could not execute statement; bad SQL grammar [n/a]; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order) values (0, 'Mortal', 0)' at line 1
at ...
order is a SQL keyword. You need to rename or remap that property.
static mapping = {
order column: "my_order"
}
Because order is a reserved word you have to use some other name for your column. Or surround your column name with backticks
Example
static mapping = {
order column: "order_col"
}
or
static mapping = {
order column: "`order`"
}
Given below domain class, I want to convert the Blob data into Java object by deserializing the bytes. What is the approach to follow? Do I need to specify any converter to GORM to invoke it after fetching the data from DB?
class SpringMessage {
static mapping = {
datasource 'staging_oracle'
message type: 'blob', column: 'message_bytes'
createdDate type: Date, column: 'created_date'
}
static constraints = {
}
String messageId
Blob message //It holds the serialized bytes
Date createdDate
}
Ideally I do not want to have "Blob" property on the domain class. Instead I want to declare actual Java class type (ex: Foo message) but hope to specify some type of converter in mapping. i.e
static mapping = {
message type:'blob', converter:FooDeserializer
}
Note the converter argument for message column in mapping. Is there such a feature in Grails? Or any other feature which allows me to do some post processing after the data is fetched from GORM?
I use Grails 2.3.3.
As of now, I do the deserialization outside the grails generated findBy method. I was hoping that there would be some callback method which might be called by findByXXX implementations to do this convertion from Blob to java object.
ObjectInputStream is = new ObjectInputStream(springMessage.message.getBinaryStream())
Message<?> message = is.readObject()
So I have a grails model looking like this:
class Tree {
Long id;
String name;
static hasMany = [branches: Branch
}
class Branch {
Long id;
String name;
static belongsTo = [tree: Tree]
}
The issue is that in DB, the Tree id is a number, while the tree_id in the Branch table is a varchar.
The db model is a de facto foreign key that is not enforced at all as a constraint.
Gorm generate a query that is not handled by my database: it tries to bing a numerical value where text is expected:
ERROR util.JDBCExceptionReporter - ORA-01722: invalid number
How can I tell GORM to convert the value before binding the parameter ?
I looked in the join table config but I did not find anything relevant.
Have you tried using sqlType in column definition in mappings?
class Branch {
Long id
String name
static belongsTo = [tree: Tree]
static mapping = {
tree column: 'TREE_ID', sqlType: "char"
}
}
Unable to test right now, but do let me know.
With the code below I am having an issue where not all the columns are return data in the data.results array. For example if col4 is null in the database for row 1 then data.results[0] does not contain an element for col4, but row 2 has a value then data.results[1] will contain the value for col4. I would like each return item in the array to contain all items with the database value or null. If null can't be returned then an empty string would do.
var query = new breeze.EntityQuery()
.from('mytable')
.where('col1', 'substringof', '2')
.select('col1,col2,col3,col4')
.orderBy('col1')
.take(200);
return _manager
.executeQuery(query)
.then(function (data) {
return data.results;
})
.fail(queryFailed);
}
By default breeze does not serialize null values in its JSON results. This was a deliberate choice to reduce the breeze payload over the wire. And.. this is not typically an issue with queries that return "entities". i.e. data for which breeze metadata exists. Because the properties are already defined on such entities.
But if you are returning an anonymous result as you are, then this can be a problem. You can obviously work around it because you know the properties that you are requesting and can update them after the query if they are not in the result.
But you can also change breeze's default configuration to accommodate this via the "BreezeConfig" class.
BreezeConfig enables customization of components supporting Breeze-related operations in the Web API. BreezeConfig defines default behaviors; you can substitute your own behaviors by deriving from it and overriding its virtual methods. Breeze.NET will discover your subclass among the assemblies referenced by your project and use it instead of BreezeConfig.
To use BreezeConfig to configure the Json.Net serializer with specific settings. You can replace those settings by writing a subclass of BreezeConfig that overrides the 'CreateJsonSerializerSettings' method as shown in this example:
public class CustomBreezeConfig : Breeze.WebApi.BreezeConfig {
protected override JsonSerializerSettings CreateJsonSerializerSettings() {
var baseSettings = base.CreateJsonSerializerSettings();
baseSettings.NullValueHandling = NullValueHandling.Include;
return baseSettings;
}
Hope this helps.
At general I have problem with mapping: org.hibernate.MappingException: Repeated column in mapping for entity: os.comida.StoreDocumentRw column: Type (should be mapped with insert="false" update="false")
To introduce the problem: I have many document types where each document type differs just a little bit from any other. All of them have common properties: date, number, issuer etc. So I decided store all document types in one physical table (one - it's very important for me). To implement this I wanted use hibernate discriminator.
Below I'm pasting my source code. I have there a base class StoreDocument and two document types StoreDocumentRw and StoreDocumentWz.
class StoreDocument {
String type
Date documentDate
static mapping = {
table '"StoreDocument"'
version false
id column:'"StoreDocumentID"', generator:'sequence', params:[sequence:'STORE_DOCUMENT_SEQ']
discriminator column: '"Type"'
documentDate column:'"DocumentDate"'
type column:'"Type"'
}
}
class StoreDocumentRw extends StoreDocument {
String rwSpecificData
static mapping = {
discriminator value: 'rw'
rwSpecificData column:'"RwSpecificData"'
}
}
class StoreDocumentWz extends StoreDocument {
String wzSpecificData
static mapping = {
discriminator value: 'wz'
wzSpecificData column:'"WzSpecificData"'
}
}
And when I'm trying run an app I get mentioned earlier org.hibernate.MappingException: Repeated column in mapping for entity: os.comida.StoreDocumentRw column: Type (should be mapped with insert="false" update="false")
When I add type insertable: false, updateable: false to StoreDocumentRw mapping, it's still the same.
When I add type insertable: false, updateable: false to StoreDocument mapping, it's even worse:
ERROR hbm2ddl.SchemaExport - Unsuccessful: create table COMIDA2."StoreDocument" ("StoreDocumentID" number(19,0) not null, "DocumentDate" timestamp not null, "Type" varchar2(255 char), "Type" varchar2(-1 char) not null, "WzSpecificData" varchar2(255 char), "RwSpecificData" varchar2(255 char), primary key ("StoreDocumentID"))
ERROR hbm2ddl.SchemaExport - ORA-00957: duplicate column name
So I don't know where I should put this insertable/updateable thing to make it working. I'm using Grails 2.0.0 and Oracle 10g. Can anybody tell me what's wrong with my code?
Solution:
My mapping in StoreDocument was wrong. It's enough to cut String type and type column:'"Type"' and edit discriminator mapping like this discriminator column:[name:'"Type"',length:50], which in result gives:
class StoreDocument {
Date documentDate
static mapping = {
table '"StoreDocument"'
version false
id column:'"StoreDocumentID"', generator:'sequence', params:[sequence:'STORE_DOCUMENT_SEQ']
discriminator column:[name:'"Type"',length:50]
documentDate column:'"DocumentDate"'
}
}
In StoreDocument you have String type and discriminator column: '"Type"', so from your exceptions it looks like Grails is trying to create two columns of name type. Try changing the name of your String or discriminator-column and see if that corrects the issue.
To get around the issue with generating a varchar(-1) field try this: discriminator column:[name:'Type',length:10] from this JIRA GRAILS-5168. Of course, change the length to whatever you need.