I am attempting to map a few legacy (existing) tables to my domain objects.
So far with a single 1:n mapping it has worked rather well. I have an ITEM or YFS_ITEM table which maps to an Item_Alias (YFS_ITEM_ALIAS) table (OmsItemAlias). My objects look are shown below for the associations.
When I create or do a find on OmsItem I do indeed get back OmsItemAlias objects. The problem comes in when I look for inventoryItems for some reason this is always 0, even though I know there are items which exist in the database.
package com.fheg.orderrouter
class OmsItem {
String id
String item
String description
Double unitCost
String defaultProductClass
String organizationCode
static hasMany = [ aliases : OmsItemAlias,inventoryItems : InventoryItem]
static constraints = {
id(blank: false, nullable: false)
item(nullable: false)
description(nullable: false)
unitCost(nullable: false)
defaultProductClass(nullable: false)
organizationCode(nullable: false)
}
static mapping = {
table 'YFS_ITEM'
version false
id column:'ITEM_KEY', generator:'assigned', sqlType: 'char(24)'
item column: 'ITEM_ID', sqlType: 'char(40)'
description(column: 'DESCRIPTION', sqlType: 'varchar2(500)')
unitCost column: 'UNIT_COST', sqlType: 'NUMBER(19,6)'
defaultProductClass column: 'DEFAULT_PRODUCT_CLASS',sqlType: 'char(10)'
organizationCode column: 'ORGANIZATION_CODE', sqlType: 'char(24)'
aliases(sort:'aliasName', fetch: 'eager')
inventoryItems( fetch: 'eager')
}
}
Here is the code for InventoryItem.
package com.fheg.orderrouter
class InventoryItem {
String id
String organizationCode
String uom
String productClass
static belongsTo = [ invItem : OmsItem ]
static hasMany = [ inventorySupply : InventorySupply]
static constraints = {
id(blank: false, nullable: false)
organizationCode(nullable: false)
// invItem(nullable: false)
uom(nullable: false)
productClass(nullable: false)
}
static mapping = {
table 'YFS_INVENTORY_ITEM'
version false
id column:'INVENTORY_ITEM_KEY', generator:'assigned'
invItem column: 'ITEM_ID'
organizationCode column: 'ORGANIZATION_CODE'
uom column: 'UOM'
productClass column:'PRODUCT_CLASS'
}
}
I am quite sure what I am doing wrong with regard to the belongTo/hasMany. It works fine for the aliases, but does nothing for inventoryItems. Any suggestions is are appreciated!
A couple of things I can see.
I don't think fetch: 'eager' is a valid fetch type: see Fetch
what you might want is: lazy: false , see Fetching
Now also I think you might want a join and not selects, so maybe (this I can't guarantee is totally correct but I hope puts u on the right track):
inventoryItems column: 'ITEM_ID', ignoreNotFound: true, fetch: 'join'
and in the InventoryItem mapping:
invItem column: 'ITEM_ID'
Now maybe turn on Hibernate logging to see what is going on at the SQL level
In Config.groovy find the log4j setup and add:
trace 'org.hibernate.SQL'
Also see FAQ
on top of that you turn on SQL logging on a per datasource setting
...or even try the P6 plugin if necessary, that should give you more of a hint as to what SQL is being generated
Sorry for the reference overkill
Related
I keep getting error after error when trying to map a many to many relationship between two tables where the joinTable has a composite id. I have three domains. One of them is a join table with composite id, the other two have a many to many relationship between each other through this intermediary table.
Demo Code on Github
Foreign key error
I'm upgrading an internal project to the last version of Grails, and I'm fixing the errors as they show up. I'm stuck with the error below but I still don't know the cause of it. In the demo code I get the same error only when static belongsTo = [areas: Professional] is set on the Area domain:
Foreign key (professional_area [])) must have same number of columns as the referenced primary key (area [id_area])
Query errors
If I remove the line with belongsTo in the Area domain, most of the code works, but I still get an error when trying to use Area.findAllByProfessionalsInList, for instance:
2022-08-30 15:27:26.930 ERROR --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : Parameter "#1" is not set; SQL statement:
select this_.id_area as id_area1_0_0_, this_.name as name2_0_0_ from area this_ where this_.id_area in (?) [90012-200]
org.hibernate.exception.DataException: could not extract ResultSet
ProfessionalArea.findAll() returns an empty list
Area:
class Area implements Serializable {
String id
String name
static belongsTo = [areas: Professional]
static hasMany = [
professionals: Professional,
]
static mapping = {
id column: "id_area", generator: "assigned"
version false
professionals joinTable: [name: 'professional_area', key: 'id_area']
}
}
Professional:
class Professional implements Serializable {
String name
static hasMany = [
areas: Area,
]
static mapping = {
id column: "id_professional", generator: "assigned"
tablePerHierarchy false
version false
areas joinTable: [name:'professional_area', key: 'id_professional']
}
}
ProfessionalArea:
import org.apache.commons.lang.builder.HashCodeBuilder
class ProfessionalArea implements Serializable {
String activities
Area area
Professional professional
static mapping = {
table name: "professional_area"
id composite: ['professional', 'area']
version false
professional column : 'id_professional', lazy: false
area column: 'id_area', lazy: false
}
}
Ideally, as I understand, the domains mapped should pass the following test (also on the demo code):
void "composite key working"() {
when:
def prof = Professional.findById(1395)
def area = Area.findById("TEST_AREA")
def area2 = Area.findById("TEST_AREA2")
then: "Professional is in 2 areas"
prof.areas.size() == 2
and: "professional contains area"
prof.areas.containsAll([area, area2])
and: "ProfessionalArea Contains 2 rows"
ProfessionalArea.count() == 2
and: "Can find area by professionals"
Area.findAllByProfessionalsInList([prof], [fetch: [professionals: 'join']]).size() == 2
and: "Can find Professionals by area"
Professional.findByAreasInList([area], [fetch: [areas: 'join']])?.id == 1395
and: "Can get Professionals through joinTable ProfessionalArea"
1395 in ProfessionalArea.findAllByArea(area2)*.professional*.id
and: "Can get Area through joinTable ProfessionalArea"
"TEST_AREA2" in ProfessionalArea.findAllByProfessional(prof)*.area*.id
}
I have two domain classes and the relationship between them is 1 to Many. I know how to map the columns of each class to their respective tables individually but how do I map the relationship that exists in my MSSQL database? There is no join table and I only have read-only access. I've looked at various pages of the Grails documentation and this is where I am at at the moment (One student has many courses). In my tables the foreign key that ties the two tables together is in the Courses table.
class StudentHeader { //on the one side
String stuNo
String stuName
String stuStreet
static mappedBy = [refs: "fkCustNo"]
static hasMany = [refs: CourseHeader]
static constraints = {
}
static mapping = {
table name: "[tbl_Students]", schema: "[dbo]", catalog: "[CRD].[CourseTrak]"
version false
id generator: 'assigned', name: 'stuNo'
stuNo column: '[PK_StudentNo]'
stuName column: '[Student_Name]'
stuStreet column: '[Student_Street]'
}
}
class CourseHeader { //on the many side
String courId
String courName
StudentHeader fkCourNo
static constraints = {
}
static mapping = {
table name: "[tbl_Courses]", schema: "[dbo]", catalog: "[CRD].[CourseTrak]"
version false
id generator: 'assigned', name: 'courId'
courId column: '[PK_CourseId]'
courName column: '[Course_Name]'
fkCourNo column: '[FK_CourseNo]'
}
}
As a test here is how I am trying to access the courses of a student
StudentHeader.first().refs
I believe I have it figured out. For the domain class on the many side you need to set insert and updateable equal to false like so (The object field in your "many" domain has to have the same value as the key-value pair in mappedBy):
class StudentHeader{
static mappedBy = [refs: "fkCustNo"]
}
class CourseHeader { //on the many side
String courId
String courName
StudentHeader fkCustNo
static constraints = {
}
static mapping = {
table name: "[tbl_Courses]", schema: "[dbo]", catalog: "[CRD].[CourseTrak]"
version false
id generator: 'assigned', name: 'courId'
courId column: '[PK_CourseId]'
courName column: '[Course_Name]'
fkCustNo column: '[FK_CourseNo]', insertable: false, updateable: false
}
}
So I have my domain below. Basically what I'm wanting to do is get all the demandNumbers for a given WorkOrderSummary. The data structure exists in the view already so I really just need to figure how I can map the WorkOrder-to-demandNumbers (one-to-many) relationship. So one lab_order_header_id can contain many demand_header_ids.
class WorkOrderSummary {
String workOrderNumber
Long demandNumbers
String demandTypeName
String statusName
Date needByDate
String customerName
Long facilityId
Long labDestinationId
Long assetTagQuantity
static hasMany = [demandNumbers: WorkOrderSummary]
static mapping = {
version false
table name: 'work_orders_v', schema: 'lab'
id column: 'lab_order_header_id'
demandNumbers column: 'demand_header_id'
demandTypeName column: 'demand_type'
statusName column: 'status'
}
}
Right now this is only getting me one-to-one in terms of lab_order_header_id to demand_header_ids.
Any suggestions?
Edit - 9/26/2016
Created another domain class for the demandNumbers and notated the belongsTo:
class SalesOrderSummary {
String demandTypeName
static belongsTo = [workOrder: WorkOrderSummary]
static constraints = {
}
static mapping = {
version false
table name: 'work_orders_v', schema: 'lab'
id column: 'demand_header_id'
workOrder column: 'lab_order_header_id'
demandTypeName column: 'demand_type'
}
}
If you wish to have many DemandNumbers for a given WorkOrderSummary
you should replace the following line:
static hasMany = [demandNumbers: WorkOrderSummary]
With the following line:
static hasMany = [demandNumbers: DemandNumbers]
It should make the relationship you are trying to get.
I have a domain class:
class Author {
String name
static hasMany = [superFantasticAndAwesomeBooks: Book, superBadAndUltraBoringBooks: Book]
}
This is all nice when using the in-memory database, however, when running on Oracle the Book collections are modeled in a join table which cannot be created because the column names are too long.
So, then I tried specifying the join table properties:
static mapping = {
superFantasticAndAwesomeBooks joinTable: [key: awesomeBooks]
superBadAndUltraBoringBooks joinTable: [key: boringBooks]
}
The problem (which doesn't happen if joinTable isn't specified) is that the join table is created where columns correspoinding to awesomeBooks and boringBooks are NOT NULL (they need to be nullable because a Book will be an awesomeBook or a boringBook)
Is there any way to configure joinTable to allow NULL columns?
Another option would be to map the join table yourself with a Domain class, for example:
class AuthorBook {
Author author
Book book
String status
static constraints = {
author(nullable:false)
book(nullable:false)
status(nullable:false,inList:['SuperFantasticAndAwesome','SuperBadAndUltraBoring'])
}
}
So your Author class becomes:
class Author {
...
static hasMany = [authorBooks:AuthorBook]
}
In this way the status is stored as a value of the join and statuses can be added, updated, or removed as needed in the future. It does have the side effect of having to query through the AuthorBook class to get to the associated Books.
See also: http://grails.org/Many-to-Many+Mapping+without+Hibernate+XML
I just ended up using 2 join tables:
static mapping = {
superFantasticAndAwesomeBooks joinTable: [name: 'awesomeBooks', key: 'book_id']
superBadAndUltraBoringBooks joinTable: [name: 'boringBooks', key: 'book_id']
}
I've read through a lot of posts and docs and must be missing something.
In my application (model below) I am having a data issue that seems to be out of my control where I have a categoryId in the join table JOBORDERCATEGORIES that has no corresponding row in the CATEGORY table. I am accessing the category data through getJobCategories() in the JobOrder. This is producing the following error when my Category table is missing a referenced row:
2012-03-07 08:02:10,223 [quartzScheduler_Worker-1] ERROR listeners.SessionBinderJobListener - Cannot flush Hibernate Sesssion, error will be ignored
org.hibernate.ObjectNotFoundException: No row with the given identifier exists: [com.matrixres.domain.Category#416191]
and my code is halting.
I have tried using ignoreNotFound but it is not helping me to get past the error above.
If I missed a post on the solution to this problem please link me to it otherwise thoughts are welcome on how to move forward. Perhaps there is a more direct route I will have to hammer in to achieve my goal of getting a good category list, but I am not familiar enough with the framework to know what is next. As a note, I cannot write to any of these tables.
thanks, rich
A simplified Version of my Model:
Job Order Object:
class JobOrder {
def getJobCategories() {
def cats = []
try {
def jocategories = this.categories
if(!jocategories.isEmpty() && jocategories!=null){
println "we got categories for ${this.id}"
jocategories.each { cat ->
if(cat?.parentCategoryID == 0){
if(cat.occupation != null){
cats << cat.occupation
} else {
cats << cat.name
}
}
}
}
} catch(e) {
cats << "Other Area(s)"
}
cats
}
static mapping = {
table 'dbo.JOBORDER'
version false
id generator: 'identity', column: 'JOBORDERID'
/*
* several other mapped columns deleted here
*/
categories joinTable:[name:'jobOrderCategories', column: 'categoryId', key:'jobOrderID']
}
/*
* several properties deleted here
*/
static hasMany = [categories: Category] //several other hasMany associations exist
}
Category Object:
class Category {
static mapping = {
table 'CATEGORY'
version false
id generator: 'identity', column: 'categoryID'
occupation column: 'OCCUPATION'
name column: 'NAME'
parentCategoryID column: 'PARENTCATEGORYID'
/*
* several other mapped columns deleted here
*/
jobOrders joinTable:[name:'jobOrderCategories', column: 'jobOrderID', key:'categoryId']
}
String name
String occupation
int parentCategoryID
/*
* several properties deleted here
*/
static belongsTo = [JobOrder]
static hasMany = [jobOrders:JobOrder]
}
Join Table:
class JobOrderCategories {
static mapping = {
table 'JOBORDERCATEGORIES'
version false
isDeleted column: 'ISDELETED'
jobOrderID column: 'JOBORDERID'
categoryId column: 'CATEGORYID'
}
Boolean isDeleted
Integer jobOrderID
Integer categoryId
}
These kinds of situations aren't the most fun, but I have had to deal with this kind of Roll-Your-Own ORM problems before ;) Basically what you're going to want to do here is store the object properties not typed as Object references, but as ints, and while you'll lose some of the dynamic finder things GORM makes so nifty, you'll have a fairly straight-forward means of accessing the data that doesn't involve tangling yourself up with Hibernate's innards.
Basically, this will involve ditching your hasMany and belongsTo properties on JobOrder and Category. Instead, you'll want to do things like
def myJobOrder = JobOrder.get(yourId);
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(myJobOrder.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
You can put variations of those traversals in helper methods on your classes, like for example, your getJobCategories method could become
class JobOrder {
//...
def getJobCategories() {
def myCategoryIds = JobOrderCategories.findAllByJobOrderID(this.id)
def myCategories = Categories.withCriteria {
in('id', myCategoryIds)
}
}
}
And so on. This is definitely not the prettiest thing in the world to deal with, and you lose your ability to traverse through things easily with GORM (ex a
jobOrder.withCriteria {
categories {
eq('name', blah)
}
}
becomes a executeQuery type of situation.)
But overall, its not too bad to deal with :)
Hope that helps!