How to avoid ImprovedNamingStrategy in joinTable in Grails - grails-orm

I have a legacy database which I can't change and I have this setup
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId', column: 'BarId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
}
}
When i try to access the method getBarName() in a controller Hibernate translates the inverse column name to "bar_id". Is there some way to set up a mapping like the one for the id and property columns?
And on a side note. How do i correctly implement getBarName()? Thacan't possibly be the correct implementation...
*EDIT*
----------------------------------------------------------------------
Apparently I was unclear above. The thing is that i already have a join column which has the form
-------------------
|RowId|FooId|BarId|
-------------------
| 1 | abc | 123 |
-------------------
Benoit's answer isn't really applicable in this situation since I want to avoid having a domain object for the joinTable.
*EDIT 2*
----------------------------------------------------------------------
Solved it. Dont understand it though... But split the join table information between the two domain classes and it works...
class Foo {
static hasMany = [bars:Bar]
static mapping = {
version false
columns {
id column: "FooId"
color column: "FooColor"
bars joinTable: [name: "FooBar", key: 'FooId']
}
transient
def getBarName(){
((Bar)this.bars.toArray()[0]).name
}
}
class Bar {
static hasMany = [foos:Foo]
static belongsTo = [Foo, Baz]
static mapping = {
version false
columns {
id column: "BarId"
name column: "BarName"
bars joinTable: [name: "FooBar", key: 'BarId']
}
}

As stated in the documentation Many-to-One/One-to-One Mappings and One-to-Many Mapping :
With a bidirectional one-to-many you can change the foreign key column
used by changing the column name on the many side of the association
as per the example in the previous section on one-to-one associations.
However, with unidirectional associations the foreign key needs to be
specified on the association itself.
Thye given example is:
class Person {
String firstName
static hasMany = [addresses: Address]
static mapping = {
table 'people'
firstName column: 'First_Name'
addresses column: 'Person_Address_Id'
}
}

Related

Laravel 8 left join where clause is ambiguous

I'm trying to use leftJoin but I'm having problem with conflicts between equal names in both tables.
Tables
products: added_by - user_id - published - approved - featured
product_types: added_by - user_id - published - approved - featured
$products = ProductType::
leftJoin('products', 'products.product_type_id', '=', 'product_types.id')
->select('product_types.*')
How to solve this problem?
You should start by implementing the correct relationships, it will help you in the future.
class ProductType extends Model {
public function products(): HasMany
{
return $this->hasMany(Product::class);
}
// Add the other missing relations too (user, etc)
}
class Product extends Model {
public function productType(): BelongsTo
{
return $this->belongsTo(ProductType::class);
}
// Add the other missing relations too (user, etc)
}
You can now use:
// Query all Product Types with related products
$productTypes = ProductType::with('products')->get();
// Query all ProductTypes that have products
$productTypes = ProductType::withWhereHas('products')->get();
/** #var ProductType $productType */
foreach ($productTypes as $productType) {
// You have access to the collection of associated products, in memory
$productType->products;
}
More information on eloquent relationships in https://laravel.com/docs/9.x/eloquent-relationships

GORM Domain Mapping Issue

I've got a bit of a complicated domain model I'm trying to implement and I'm having some trouble. (On top of that, I'm quite new to all this!)
I have a User domain which has multiple roles and multiple tests. The Role domain works great. The Test domain is a bit more compilciated though because it requires two foreign keys instead of just 1 like in the Role domain. The first foreign key is the user_id and the second is a uni_id (university ID).
The User domain model contains the following
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
...
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
tests joinTable:[name:'user_test', key:'user_id'] // Here is where I run into trouble
}
static constraints = {
}
}
The Test domain contains
class Test {
static belongsTo = User
static hasMany = [users:User]
static hasOne = [uni:Uni]
Integer testId // primary key
String testType
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
users joinTable:[name:'user_test', key:'test_id']
uni joinTable:[name:'user_test', key:'test_id'] // If I leave this out, everything is groovy
version false
}
static constraints = {
}
}
and the Uni domain contains
class Uni {
static belongsTo = Test
static hasMany = [tests:Test]
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
tests joinTable:[name:'user_test', key:'uni_id']
}
static constraints = {
}
}
If its not clear, what I'm trying to do is pull in the University ID, Test ID, and User ID to a table user_test to find based on the User ID which tests they have taken. Is there a simple way to do this?
The kinds of errors I'm getting lead me to believe that for some reason it is trying to perform all actions on the table test instead of user_test. For example,
Unsuccessful: alter table test add uni_id int not null
I'd like to be able to access the test and university information corresonding to the specific user via user.tests.testType and user.tests.uni.fullName or something to that extent. What am I doing wrong? More importantly, is there a better way to do this?! Thanks in advance!
Edit 1: something interesting I just thought of.. a user can have multiple tests, but the inverse isn’t true. A given test will never be shared among multiple people. So I think that changes things a bit.. I'll do some reading and post if I come up with anything new.
Edit 2: Here's the Role domain
class Role {
static belongsTo = User
static hasMany = [users:User]
Integer roleId
String shortName
String roleName
Integer roleLevel
static mapping = {
table 'role'
id generator: 'assigned', name: 'roleId', type: 'long'
roleId column: 'role_id'
users joinTable:[name:'user_role', column:'user_id', key:'role_id']
version false
}
static constraints = {
}
}
Edit 3: I am now trying to store all test information in the Test domain model and simply choose the Uni name to store as a field in Test, but am getting weird errors when I try this. My new files look like this
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', key:'user_id']
}
static constraints = {
}
}
and
class Test {
static belongsTo = User
Integer testId // primary key
Integer testTypeId
String testTypeName
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
but now I'm getting the following error when I try to run it Caused by: org.hibernate.MappingException: Missing type or column for column[tests_test] on domain[User] referencing[Test]
Any idea what that's about?
Ok, one issue you have is that you're trying to share the User-to-Test association join table with the Test-to-Unit association. That's not going to work.
Lets look at it in database terms. I'm not an ASCII art expert, so I hope this diagram doesn't make your eyes bleed.
user_data (userId) |---|< (user_id) user_test (test_id) >|---| (testId) test
The diagram above shows the database implementation of the many-to-many association between the User and Test domain classes. You can see that the user_data.userId links to user_test.user_id and user_test.test_id links to test.testId.
Now here's where it starts to get weird. There are two different associations between Test and Uni: a bidirectional one-to-one and a one-to-many. I just don't understand that. But I want to illustrate an important issue with your join tables, so here it is.
test (testId) |---|< (test_id) user_test (uni_id) >|---| (uniId) uni
Because you're using the same join table (user_test) for two different associations you're asking GORM to create a table like this:
USER_TEST
- USER_ID
- TEST_ID
- UNIT_ID
GORM won't do that because join tables are supposed to have only two fields. Not only that, but also you're defining a many-to-many in database terms, and yet a bidirectional one-to-one and a one-to-many in GORM terms. Ouch!
TODO
The first change I recommend is to use a different join table for the Test-Uni association.
Finally got everything working (after a bit of modification in terms of the domain model)
class User {
static hasMany = [roles:Role, tests:Test]
Integer userId
static mapping = {
table 'user_data'
id generator: 'assigned', name: 'userId', type: 'long'
userId column: 'user_id'
version false
roles joinTable:[name:'user_role', column:'role_id', key:'user_id']
}
static constraints = {
}
}
and
class Test {
User user
Integer testId // primary key
String testType
String testUni
Date testDate
static mapping = {
table 'test'
id generator: 'assigned', name: 'testId', type: 'long'
testId column: 'test_id'
version false
}
static constraints = {
}
}
with
class Uni {
Integer uniId // primary key
String shortName
String fullName
static mapping = {
table 'uni'
id generator: 'assigned', name: 'uniId', type: 'long'
uniId column: 'uni_id'
version false
}
static constraints = {
}
}
So now what I'm doing is selecting the university from a drop down tab in my GSP and just saving it in Test as the string testUni. Then, the big change was removing all joinTables between the three and adding User user to Test. I'm still a little fuzzy on why what I was doing before didn't work, but I won't complain about a working app!

Grails query to filter on association and only return matching entities

I have the following 1 - M (one way) relationship:
Customer (1) -> (M) Address
I am trying to filter the addresses for a specific customer that contain certain text e.g.
def results = Customer.withCriteria {
eq "id", 995L
addresses {
ilike 'description', '%text%'
}
}
The problem is that this returns the Customer and when I in turn access the "addresses" it gives me the full list of addresses rather than the filtered list of addresses.
It's not possible for me to use Address.withCriteria as I can't access the association table from the criteria query.
I'm hoping to avoid reverting to a raw SQL query as this would mean not being able to use a lot functionality that's in place to build up criteria queries in a flexible and reusable manner.
Would love to hear any thoughts ...
I believe the reason for the different behavior in 2.1 is documented here
Specifically this point:
The previous default of LEFT JOIN for criteria queries across associations is now INNER JOIN.
IIRC, Hibernate doesn't eagerly load associations when you use an inner join.
Looks like you can use createAlias to specify an outer join example here:
My experience with this particular issue is from experience with NHibernate, so I can't really shed more light on getting it working correctly than that. I'll happily delete this answer if it turns out to be incorrect.
Try this:
def results = Customer.createCriteria().listDistinct() {
eq('id', 995L)
addresses {
ilike('description', '%Z%')
}
}
This gives you the Customer object that has the correct id and any matching addresses, and only those addresses than match.
You could also use this query (slightly modified) to get all customers that have a matching address:
def results = Customer.createCriteria().listDistinct() {
addresses {
ilike('description', '%Z%')
}
}
results.each {c->
println "Customer " + c.name
c.addresses.each {address->
println "Address " + address.description
}
}
EDIT
Here are the domain classes and the way I added the addresses:
class Customer {
String name
static hasMany = [addresses: PostalAddress]
static constraints = {
}
}
class PostalAddress {
String description
static belongsTo = [customer: Customer]
static constraints = {
}
}
//added via Bootstrap for testing
def init = { servletContext ->
def custA = new Customer(name: 'A').save(failOnError: true)
def custB = new Customer(name: 'B').save(failOnError: true)
def custC = new Customer(name: 'C').save(failOnError: true)
def add1 = new PostalAddress(description: 'Z1', customer: custA).save(failOnError: true)
def add2 = new PostalAddress(description: 'Z2', customer: custA).save(failOnError: true)
def add3 = new PostalAddress(description: 'Z3', customer: custA).save(failOnError: true)
def add4 = new PostalAddress(description: 'W4', customer: custA).save(failOnError: true)
def add5 = new PostalAddress(description: 'W5', customer: custA).save(failOnError: true)
def add6 = new PostalAddress(description: 'W6', customer: custA).save(failOnError: true)
}
When I run this I get the following output:
Customer A
Address Z3
Address Z1
Address Z2

Fluent NHibernate: Custom ForeignKeyConvention not working with explicitly specified table names

EDIT: for the tl;dr crowd, my question is: How do I access the mappings from inside the ForeignKeyConvention in order to determine the table name that a given type is mapped to?
The long version:
I am using Fluent NHibernate to configure NHibernate, and I have a custom foreign key convention that is failing when I alias tables and columns.
My tables use a convention where the primary key is always called "PK", and the foreign key is "FK" followed by the name of the foreign key table, e.g., "FKParent". For example:
CREATE TABLE OrderHeader (
PK INT IDENTITY(1,1) NOT NULL,
...
)
CREATE TABLE OrderDetail (
PK INT IDENTITY(1,1) NOT NULL,
FKOrderHeader INT NOT NULL,
...
)
To make this work, I've built a custom ForeignKeyConvention that looks like this:
public class AmberForeignKeyConvention : ForeignKeyConvention
{
protected override string GetKeyName( Member member, Type type )
{
if ( member == null )
return "FK" + type.Name; // many-to-many, one-to-many, join
return "FK" + member.Name; // many-to-one
}
}
This works so long as my entities are named the same as the table. But it breaks when they aren't. For example, if I want to map the OrderDetail table to a class called Detail, I can do so like this:
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Table( "OrderDetail" );
Id( o => o.PK );
References( o => o.Order, "FKOrderHeader" );
...
}
}
The mapping works for loading a single entity, but when I try to run any kind of complicated query with a join, it fails, because the AmberForeignKeyConvention class is making incorrect assumptions about how the columns are mapped. I.e., it assumes that the foreign key should be "FK" + type.Name, which in this case is Order, so it calls the foreign key "FKOrder" instead of "FKOrderHeader".
So as I said above: My question is, how do I access the mappings from inside the ForeignKeyConvention in order to determine a given type's mapped table name (and for that matter, their mapped column names, too)? The answer to this question seems to hint at the right direction, but I don't understand how the classes involved work together. When I look through the documentation, it's frightfully sparse for the classes I've looked up (such as the IdMapping class).
the idea is to load the mappings
public class AmberForeignKeyConvention : ForeignKeyConvention
{
private static IDictionary<Type, string> tablenames;
static AmberForeignKeyConvention()
{
tablenames = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => typeof(IMappingProvider).IsAssignableFrom(t))
.ToDictionary(
t => t.BaseType.GetGenericArguments()[0],
t => ((IMappingProvider)Activator.CreateInstance(t)).GetClassMapping().TableName);
}
protected override string GetKeyName( Member member, Type type )
{
return "FK" + tablenames[type]; // many-to-one
}
}

NHibernate does not filter data base on COORECT TYPE of meta data

I have an interface (IContactable) which is realizing by 3 classes : Person, Department, RestUnit
public interface IContactable
{
Contact Contact { get; set; }
string Title { get; }
int? Id { get; set; }
}
public class Person:IContactable
public class Department:IContactable
public class RestUnit:IContactable
There is another class, Contact, which should maintain which one of these objects are the owner of the contact entity.
A part of Contact mapping which does the job is:
ReferencesAny(p => p.Contactable)
.EntityTypeColumn("ContactableType")
.EntityIdentifierColumn("ContactableId")
.IdentityType<int>()
.AddMetaValue<Person>("Person")
.AddMetaValue<Department>("Department")
.AddMetaValue<RestUnit>("RestUnit");
So that Contact records in database would be like (The types are being saved as string):
X Y ContactableType ContactableId
... ... Person 123
... ... Person 124
... ... Department 59879
... ... RestUnit 65
... ... Person 3333
... ... Department 35564
Everything works just fine but filtering data. When I want to get some particular Contacts, say with Department type, I would write something like :
var contacts = Repository<Contact>.Find(p=>p is Department);
Nhibernate tries to filter data based on ContactableType field with an integer value but the ContactableType column is nvarchar
Generated query by NHibernate :
select .......... from contact.[Contact] where ContactableType=1
Expected query:
select .......... from contact.[Contact] where ContactableType='Department'
So NHibernate kinda using a wrong type. int instead of string.
I think NH is using the index of the object in list which AddMetaValue("Department") has added department type into...
I hope the explanation would be clear enough
I'm using NH3....
any idea?
Have you tried to add an extra line:
ReferencesAny(p => p.Contactable)
.MetaType<string>()