Grails - [1:N] Relationship issue - mysql

I have 2 different domain classes, one for Employee and one for Departments. The relationship between them is [1:N],which means that many employees can work at one Department but not vice versa. The issue is that, after Grails creates the tables from the domain classes when the project runs, for one employee, the department Id of that table references the id on Department Table. For example, for a user named "Peter", department id would be 1.
The department table also has names for the departments, along with the department id's.
How can I reference the department_id in employee table to point at department.name instead of department.id ?
Department Domain Class :
class Department {
String name
static hasMany = [
employees: Employee
]
static constraints = {
}
static mapping = {
version false
}
def String toString() {
name
}
}
Employee Domain Class :
class Employee {
String firstName
String lastName
String email
String country
int born
static belongsTo = [department: Department]
static constraints = {
firstName(blank: false)
lastName(blank: false)
born(blank: false)
email(blank: false, email: true)
country(blank: false)
}
static mapping = {
version false
}
}
What I need is, when in Employee table, the department_id column to reference at department.name instead of department.id.

I guess you need to configure that the Primary Key of the Department table is 'name' instead of the default 'id' column. Grails will then use the name as the Foreign Key.
i.e.:
class Department {
...
static mapping = {
id name: 'name'
}
...
}
ref (Grails 3.1) : http://docs.grails.org/3.1.x/ref/Database%20Mapping/id.html

Related

table references a table which in turn references another table

I'm using gorm, and I have this structure where the User table contains a foreign key referencing Address, which then references Country.
type User struct {
ID int `gorm:"column:id; primaryKey; autoIncrement" json:"id"`
Address Address
AddressID int `gorm:"column:address_id;foreignKey:AddressID;references:ID" json:"address"`
}
type Address struct {
ID int `gorm:"column:ID;primaryKey;autoIncrement;" json:"id"`
CountryCode int `gorm:"column:country_code; foreignKey:CountryCode; references:Code" json:"country_code"`
Country Country
}
type Country struct {
Code int `gorm:"column:code; primaryKey; autoIncrement" json:"code"`
Name string `gorm:"column:name" json:"name"`
ContinentName string `gorm:"column:continent_name" json:"continentName"`
}
relationship explained in the photo:
Now when I return a user using:
db.Model(&user).Where().First() // or Find()
I get the Address, and Country Empty, like this:
{
ID: 1,
AddressID: 2,
Address: {
// empty address.
}
}
I did create function repopulating the Address and Country records for me, similar to this:
func PopulateUser(user) User {
addr = FindAddresByID(user.ID)
cntr = FindCountryByCode(addr.Code)
addr.Country = cntr
user.Address = addr
return user
}
but my questions:
is there a function in Gorm which can do that for me without me creating the function?
can Associations help in this case?
if I want the address to be deleted when the user deleted, how I can do this in Gorm?
I tried to find answers on my own, but the documentation is a bit messy.
The documentation shows the foreign key tags to go at the struct reference.
i.e in your case those should be at Address and Country instead of AddressID and CountryCode. Something like:
type User struct {
Address Address `gorm:"foreignKey:AddressID;references:ID"`
AddressID int `gorm:"column:address_id"`
}
type Address struct {
CountryCode int `gorm:"column:country_code"`
Country Country gorm:"foreignKey:CountryCode; references:Code"`
}
Please try with those.
For
Please see eager loading here
db.Preload("User").Preload("Address").Find(&users)
You can use cascade tag on the column.
type User struct {
gorm.Model
Name string
CompanyID int
Company Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

Join 3 tables based on column values - Laravel

LEADS TABLE
id
title
owner_id
from_table
EMPLOYEE TABLE
id
first_name
last_name
role
ADMIN TABLE
id
first_name
last_name
role
$users = Leads::query();
return Datatables::make($users)
->editColumn('owner_id', function ($user) {
if($user->from_table == 'employee'){
$emp = Employee::where('id',$user->owner_id)->first();
return $emp->first_name.' '.$emp->last_name.' ('.$emp->role.')';
}
if($user->from_table == 'admin'){
$admin = Admin::where('id',$user->owner_id)->first();
return $admin->first_name.' '.$admin->last_name.' ('.$admin->role.')';
}
})
the above solutions is working fine but we are unable to search column wise induvidual searching in datatables.
what i want is join query something like:
if(leads.from_table == employee)
// fetch data from EMPLOYEE TABLE i.e. LEADS TABLE + EMPLOYEE TABLE
id
title
owner_id
from_table
first_name
last_name
role
if(leads.from_table == admin)
// fetch data from ADMIN TABLE i.e. LEADS TABLE + ADMIN TABLE
id
title
owner_id
from_table
first_name
last_name
role
I think you should change your database structure to use polymorphic relations, they fully comply with your needs - https://laravel.com/docs/5.8/eloquent-relationships#polymorphic-relationships
from_table column should contain the class name of the parent model.
Add in Leads model
public function fetchOwner()
{
return $this->morphTo();
}
Add In Employee Model
public function employee()
{
return $this->morphOne('App\Employee', 'fetchOwner');
}
Add In Admin Model
public function employee()
{
return $this->morphOne('App\Admin', 'fetchOwner');
}
$users = Leads::with('fetchOwner');
return Datatables::make($users)
->editColumn('owner_id', function ($user) {
return $user->fetchOwner->name;
})
thanks to all who tried to help..
I'm answering my own question as i found the answer after 9 days digging everywhere..
so here is the answer:
you may want to replace owner_code by owner_id in your business table.
so i changed the from_table to owner_type & owner_type now should contain the class name as value ex: changed admin to App\Admin & employee to App\Employee in Database
App\Admin.php
public function employee()
{
return $this->morphOne('App\Leads', 'owner');
}
App\Employee.php
public function employee()
{
return $this->morphOne('App\Leads', 'owner');
}
App\Leads.php
public function owner()
{
return $this->morphTo();
}
Thanks to: laravel morphTo how to use custom columns? (EddyTheDove) for pointing out the exact problem..

How to perform a cascaded merge against child of new entity with JPA and Hibernate

This question relates to managing id numbers to ensure that I don't end up with duplicate identities on my person's table. It is a springboot project with MySQL database.
Some background:
I have an HTML form for submitting an "episode". Each episode contains "persons" and has a relationship to "persons" of ManyToMany.
"episodes" are entered and submitted into the database (db1) by field staff. A few hours later the episode is manually entered into a second database (db2) by BackOffice staff.
On my spring attached database (db1) I have a persons table which has a native auto generated id field. db1 also has a id2 field - which records the unique id for the person from db2.
Field staff do not always have access to id2 when they enter a episode, but BackOffice staff do.
When I save a new "episode" I need the save method to check if person id2 exists in the database and perform an update on person (not create new).
Then delete the duplicate person.
Episode entity:
#Entity
public class Episode {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#Column(name="start_date")
#DateTimeFormat (pattern="dd/MM/yyyy HH:mm")
private Date start_date;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "episode_person", joinColumns = #JoinColumn(name = "episode_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "person_id", referencedColumnName = "id"))
private List<Person> persons;
#OneToOne(cascade=CascadeType.ALL)
//#JoinColumn(name = "id")
private Address address;
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long id2;
private String surname;
private String firstname;
private String phoneHome;
#DateTimeFormat(pattern = "dd/mm/yyyy")
private Date dob;
#ManyToMany(mappedBy = "persons", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private List<Episode> episodes;
EpisodeServiceImpl
#Override
#Transactional
public Episode saveEpisode(Episode episode) {
List mergedPeople = personService.mergeDetachedWithAttached( episode.getPersons() );
episode.setPersons( mergedPeople );
return episodeRepository.save(episode);
}
PersonServiceImpl
#Transactional
#Override
public List mergeDetachedWithAttached(List<Person> people) {
final List<Person> results = new ArrayList<>();
if ( people != null && !people.isEmpty() ) {
// loop over every person
for (Person person : people) {
// for current person try to retrieve from db via Id2
Person db2Person = personRepository.findById2( person.getId2() );
// if a person was retrieved use them instead of submitted person
if (db2Person != null ) {
System.out.println("A matching person was found in the db using id2 - this is the person that will be added");
results.add(db2Person);
} else {
results.add(person);
}
}
}
return results;
The way this is written at the moment when ever a new episode is submitted I create new person(s) even if I successfully looked them up from db1 using id2 and added them to the episode.
How can I handle this so that:
I can merge duplicate identities based on comparing id2. The joining table that holds episode_id and person_id will also need to be updated where a id is deleted after a merge.
It's much easier if you replace the #ManyToMany association with 2 bidirectional #OneToMany associations, meaning that you map the association table as well.
This way, considering that you have those duplicated Episode or Person entities, you can easily move the joined association by simply adjusting the #ManyToOne associations on the association entity. As for the duplicated Episode or Person you can either use UPSERT as explained below, or do a manual merging after the entries are added to the DB by the batch process.
Typically, when you have multiple nodes that running concurrently, you can use the database UPSERT or MERGE operation.
You could combine the UPSERT with Hibernate #SqlInsert annotation.
To handle the FK updates, you'd need to use the FK ON DELETE CASCADE strategy.

GORM Many-to-one with mapping table

I have a couple of mysql tables with many-to-one relationship which is using a mapping table for the relationship. It is as below
book_type_map
Column Datatype
book_id Char(36)
book_type_id Char(36)
book_type
Column Datatype
book_type_id Char(36)
book_type Text
default TinyInt(1)
books
Column Datatype
book_id Char(36)
As you can see, the book table or the book_type table has no columns referring eachother, I need to be able to access book_type from the book. These are my domain objects for Book and BookType
class Book {
String id
BookType bookType
static mapping = {
table 'books'
id column: 'book_id', generator: 'uuid2'
bookType joinTable: [ name: 'book_type_map',
key: 'book_id',
column: 'book_type_id']
}
}
class BookType {
String id
String type
Boolean default
static mapping = {
table 'book_type'
id column: 'book_type_id', generator: 'uuid2'
type column: 'book_type'
}
}
When I run this I get this exception when I do a Book.findAll()
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'this_.book_type_id' in 'field list'
I think my mapping is incorrect. I'd appreciate it if anyone could point me in the right direction.
Having a join table in general only makes sense for one-to-many or many-to-many relationships. In this example, Book can have only one BookType so why does there need to be a join table? You would just have a column with the BookTypeId.
I think what you are looking for is:
class Book {
String id
static hasMany = [bookType: BookType]
static mapping = {
table 'books'
id column: 'book_id', generator: 'uuid2'
bookType joinTable: [ name: 'book_type_map',
key: 'book_id',
column: 'book_type_id']
}
}
See the docs for more details
EDIT
If you are set on this table structure you can flip the relationship (not a fan of this). This structure doesn't really make sense to me, but will work:
class Book {
String id
static mapping = {
table 'books'
id column: 'book_id', generator: 'uuid2'
}
}
class BookType {
String id
String type
Boolean default
static hasMany = [books: Book]
static mapping = {
table 'book_type'
id column: 'book_type_id', generator: 'uuid2'
type column: 'book_type'
books joinTable: [ name: 'book_type_map',
key: 'book_type_id',
column: 'book_id']
}
}

Designing JPA Entity for Database Table

I am designing a voting application and designed a database with 2 tables - State and District.
The two tables are -
State details table
State_ID NUMBER(3)
State_name VARCHAR2(30)
PRIMARY KEY(State_ID)
District details table
District_ID NUMBER(3)
State_ID NUMBER(3)
District_name VARCHAR2(30)
PIN_Code NUMBER(6)
PRIMARY KEY(District_ID)
UNIQUE(State_ID, District_name)
FOREIGN KEY(State_ID)
As two states can have a district with same name, I am considering the combination of State ID and District name as UNIQUE by combination.
Now I have to design JPA Entities for these two tables, but I am unable to design because, in the database design, the District table has State ID in it as Foreign Key, but when it comes to designing entities having a State object inside District sounds meaningless, because if I keep HAS-A relationship in my mind, then District doesn't have a State in it. A State HAS-A list of Districts. This is not in accord with the above database design.
Can someone please help me in designing JPA Entities for this. Please suggest if the database design needs modification too.
Thanks.
An example JPA approach based on Rick's proposal:
#Entity
#Table(name = "States")
public class State {
#Id
#Column(nullable = false, columnDefinition = "CHAR(2) CHARACTER SET ascii")
private String code;
#Column(nullable = false, length = 30)
private String name;
#OneToMany(mappedBy = "state")
private Collection<District> districts;
public State() { }
// getters, setters, etc.
}
#Entity
#Table(name = "Districts", uniqueConstraints = {
#UniqueConstraint(columnNames = { "state_code", "name" })
})
public class District {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false, columnDefinition = "SMALLINT UNSIGNED")
private short id;
#Column(name = "state_code", nullable = false,
columnDefinition = "CHAR(2) CHARACTER SET ascii")
private String code;
#Column(length = 30)
private String name;
#ManyToOne
#JoinColumn(name = "state_id")
private State state;
public District() { }
// getters, setters, etc.
}
NOTE: due to usage of columnDefinition attribute the above entities are not portable across databases.
CREATE TABLE States (
code CHAR(2) NOT NULL CHARACTER SET ascii,
name VARCHAR(30) NOT NULL,
PRIMARY KEY(code)
);
CREATE TABLE Districts
id SMALLINT UNSIGNED NOT NULL AUTO_INCREMENT,
state_code CHAR(2) NOT NULL CHARACTER SET ascii,
...
PRIMARY KEY(id),
UNIQUE (state_code, name)
);
Notes:
* May as well use the standard state codes (AK, AL, ...) if you are talking about the US.
* Probably the only purpose of States is to spell out the name.
* I was explicit about ascii in case your table defaults to utf8.
* MySQL uses VARCHAR, not VARHAR2.
* You could get rid of Districts.id and have simply PRIMARY KEY(state_code, name), but I am guessing you have a bunch of other tables that need to join to this one, and a 2-byte id would be better than the bulky alternative.