Eclipelink #ElementCollection remove all on merge - eclipselink

I have this mapping:
#Entity
#Table(name="CUSTOMER")
class Customer {
#Id
#Column(name="CUSTOMER_ID")
Long id;
...
#ElementCollection
#CollectionTable(name="CUSTOMER_ADDRESS", joinColumns=#JoinColumn(name="CUSTOMER_ID"))
List<CustomerAddress> addresses;
}
#Embeddable
class CustomerAddress {
String street;
String zip;
String ...
String ...
...
}
I have two saved addresses for customer ID : 1. If i update this customer, having a single address on, Eclipselink tries deletes the missing address row using all CustomerAddress fields on DELETE where statement:
DELETE FROM CUSTOMER_ADDRESS WHERE STREET = ?, ..., ZIP = ?, ..., CUSTOMER_ID = ?
The issue is i may have accents and other data that prevents the WHERE statement to match the row using all fields. Is there anyway to force Eclipselink to delete all user addresses and insert again?
DELETE FROM CUSTOMER_ADDRES WHERE CUSTOMER_ID = ?
INSERT ALL FROM COLLECTION

I could not find a way to accomplish what i wanted. It seems Hibernate has the behavior i expect.
Changed ElementCollection to OneToMany and changed the model to have an ID under CustomerAddress.

Related

Simpler way to delete JPA mapped tables records

Is there any simpler way to delete the records of table with are mapped by #OneToMany and #ManyToOne JPA mapping. Currently I am using PostgreSQL as my database. There are 1000 records ranging from 0 to 999 which I want to delete, and rest I want to keep. As deleting every record with deletion of there references(foreign key) will be very hectic. Just want a simpler way to do this.
You can always execute a DELETE query in JPA:
int rowsDeleted = entityManager
.createQuery("DELETE FROM MyEntity WHERE id >= 0 AND id <=999")
.executeUpdate();
If there are foreign keys, you need some extra work:
int childRowsDeleted = entityManager
.createQuery("DELETE FROM MyChildEntity WHERE parent.id >= 0 AND parent.id <=999")
.executeUpdate();
int rowsDeleted = entityManager
.createQuery("DELETE FROM MyEntity WHERE id >= 0 AND id <=999")
.executeUpdate();
If there are many foreign keys, perhaps you can consider JPA cascade remove:
#OneToOne(cascade={CascadeType.REMOVE})
and then entityManager.remove() each entity but this will have performance implications since you will end up with many DELETE queries, each deleting a single row.
Got this with introducing #DeleteMapping, and it worked without even using
" #OnDelete(action = OnDeleteAction.CASCADE)" this annotation above the mapped variable declaration in the child class
[https://www.callicoder.com/hibernate-spring-boot-jpa-one-to-many-mapping-example/#get-paginated-posts-get-postspage0size2sortcreatedatdesc], We can follow this link to get a complete overview.
#DeleteMapping("/posts/{postId}")
public ResponseEntity<?> deletePost(#PathVariable Long postId) {
return postRepository.findById(postId).map(post -> {
postRepository.delete(post);
return ResponseEntity.ok().build();
}).orElseThrow(() -> new ResourceNotFoundException("PostId " + postId + " not found"));
}

GORM preload: How to use a custom table name

I have a GORM query with a preload that works just fine because I'm binding it to a struct called "companies" which is also the name of the corresponding database table:
var companies []Company
db.Preload("Subsidiaries").Joins("LEFT JOIN company_prod ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
Now I want to do something similar, but bind the result to a struct that does not have a name that refers to the "companies" table:
var companiesFull []CompanyFull
db.Preload("Subsidiaries").Joins("LEFT JOIN company_prod ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
I've simplified the second call for better understanding, the real call has more JOINs and returns more data, so it can't be bound to the "companies" struct.
I'm getting an error though:
column company_subsidiaries.company_full_id does not exist
The corresponding SQL query:
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_full_id" IN (2,1)
There is no "company_subsidiaries.company_full_id", the correct query should be:
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_id" IN (2,1)
The condition obviously gets generated from the name of the struct the result is being bound to. Is there any way to specify a custom name for this case?
I'm aware of the Tabler interface technique, however it doesn't work for Preload I believe (tried it, it changes the table name of the main query, but not the preload).
Updated: More info about the DB schema and structs
DB schema
TABLE companies
ID Primary key
OTHER FIELDS
TABLE products
ID Primary key
OTHER FIELDS
TABLE subsidiaries
ID Primary key
OTHER FIELDS
TABLE company_products
ID Primary key
Company_id Foreign key (companies.id)
Product_id Foreign key (products.id)
TABLE company_subsidiaries
ID Primary key
Company_id Foreign key (companies.id)
Subsidiary_id Foreign key (subsidiaries.id)
Structs
type Company struct {
Products []*Product `json:"products" gorm:"many2many:company_products;"`
ID int `json:"ID,omitempty"`
}
type CompanyFull struct {
Products []*Product `json:"products" gorm:"many2many:company_products;"`
Subsidiaries []*Subsidiary `json:"subsidiaries" gorm:"many2many:company_products;"`
ID int `json:"ID,omitempty"`
}
type Product struct {
Name string `json:"name"`
ID int `json:"ID,omitempty"`
}
type Subsidiary struct {
Name string `json:"name"`
ID int `json:"ID,omitempty"`
}
Generated SQL (by GORM)
SELECT * FROM "company_subsidiaries" WHERE "company_subsidiaries"."company_full_id" IN (2,1)
SELECT * FROM "subsidiaries" WHERE "subsidiaries"."id" IN (NULL)
SELECT companies.*, company_products.*, FROM "companies" LEFT JOIN company_products ON company_products.company_id = companies.id WHERE company_products.product_id = 1
Seems like the way to go in this case may be to customize the relationship in your CompanyFull model. Using joinForeignKey the following code works.
type CompanyFull struct {
Products []*Product `json:"products" gorm:"many2many:company_products;joinForeignKey:ID"`
Subsidiaries []*Subsidiary `json:"subsidiaries" gorm:"many2many:company_subsidiaries;joinForeignKey:ID"`
ID int `json:"ID,omitempty"`
}
func (CompanyFull) TableName() string {
return "companies"
}
func main(){
...
result := db.Preload("Subsidiaries").Joins("LEFT JOIN company_products ON company_products.company_id = companies.id").Where("company_products.product_id = ?", ID).Find(&companies)
if result.Error != nil {
log.Println(result.Error)
} else {
log.Printf("%#v", companies)
}
For more info regarding customizing the foreign keys used in relationships, take a look at the docs https://gorm.io/docs/many_to_many.html#Override-Foreign-Key

hibernate bug: How to keep Hibernate sql where condition order obey the declared order?

I have a class Student:
#Entity
#Table(name="student")
public class Student{
#EmbeddedId
private Id id;
//other property omit
#Embeddable
public static class Id implements Serializable{
#Column(name="last_name")
private String lastName;
#Column(name="first_name")
private String firstName;
}
}
I used hibernate to find a student by id, but I found that the generated sql where clause is what I don't expect. I want to generate the sql like select * from student where last_name = ? and first_name = ?, but the generated sql looks like select * from student where first_name = ? last_name = ?.
I want the generated sql where clause expression order to obey the proper order. Does anyone know how to generate such sql when I query a entity by composite id?

NHibernate, how to map a property to a subselect

I currently have a legacy system that uses SPs exclusively for access to the DB. My domain object looks something like this:
public class User : EntityBase
{
public virtual string Name {get;set;}
public virtual string CreatedBy {get;set;}
public virtual DateTime CreatedDate {get;set;}
}
The SP I have that mapped this looked like this:
CREATE PROCEDURE getUser
{
#ID int
}
select
Name
,(SELECT TOP 1 UserName FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedBy
,(SELECT TOP 1 DateStamp FROM AuditTrail WHERE EntityID = [User].[ID] AND EntityName = 'User' AND AuditActionID = 1 ORDER BY DateStamp) AS CreatedDate
FROM [User]
WHERE [User].ID = #ID
So, as you can see, the audit information is separated from the entity itself on the database and the CreatedBy/CreatedOn (and ModifiedBy/ModifiedOn) are stored in a separate table called AuditTrail. the AuditActionID field on the table specifies if it was a create/update.
How can I setup this mapping with NHibernate? I looked into JOIN but it doesn't give me the option to restrict by the additional values (and a join isn't what I want).
Also, if this is possible in Fluent NHibernate, that's a bonus but I'm fine with trying just standard NHibernate mapping config if it gets me there.
UPDATE:
I have found one way to do this, but I'm not a fan. I have setup a SQLQuery that reads the data and maps it back to an object. It works, but I'd love to do this via mapping. Is it even possible since the "values" from the database I'm mapping to is a subselect and not editable?
Solution:
Thanks to the tip from Diego, this was the final solution I found (using Fluent NHibernate, in my ClassMap file):
Map(x => x.CreatedBy).Formula("(SELECT TOP 1 AuditTrail.UserName FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Map(x => x.CreatedDate).Formula("(SELECT TOP 1 AuditTrail.DateStamp FROM AuditTrail WHERE AuditTrail.EntityID = [ID] AND AuditTrail.EntityName = 'User' AND AuditTrail.AuditActionID = 1 ORDER BY AuditTrail.DateStamp)");
Thanks,
M
You can specify the select clause as the formula for your property.

Fluent NHibernate Self Referencing Many To Many

I have an entity called Books that can have a list of more books called RelatedBooks.
The abbreviated Book entity looks something likes this:
public class Book
{
public virtual long Id { get; private set; }
public virtual IList<Book> RelatedBooks { get; set; }
}
Here is what the mapping looks like for this relationship
HasManyToMany(x => x.RelatedBooks)
.ParentKeyColumn("BookId")
.ChildKeyColumn("RelatedBookId")
.Table("RelatedBooks")
.Cascade.SaveUpdate();
Here is a sample of the data that is then generated in the RelatedBooks table:
BookId RelatedBookId
1 2
1 3
The problem happens when I Try to delete a book. If I delete the book that has an ID of 1, everything works ok and the RelatedBooks table has the two corresponding records removed. However if I try to delete the book with an ID of 3, I get the error "The DELETE statement conflicted with the REFERENCE constraint "FK5B54405174BAB605". The conflict occurred in database "Test", table "dbo.RelatedBooks", column 'RelatedBookId'".
Basically what is happening is the Book cannot be deleted because the record in the RelatedBooks table that has a RelatedBookId of 3 is never deleted.
How do I get that record to be deleted when I delete a book?
EDIT
After changing the Cascade from SaveUpdate() to All(), the same problem still exists if I try to delete the Book with an ID of 3. Also with Cascade set to All(), if delete the Book with and ID of 1, then all 3 books (ID's: 1, 2 and 3) are deleted so that won't work either.
Looking at the SQL that is executed when the Book.Delete() method is called when I delete the Book with an ID of 3, it looks like the SELECT statement is looking at the wrong column (which I assume means that the SQL DELETE statment would make the same mistake, therefore never removing that record). Here is the SQL for the RelatedBook
SELECT relatedboo0_.BookId as BookId3_
, relatedboo0_.RelatedBookId as RelatedB2_3_
, book1_.Id as Id14_0_
FROM RelatedBooks relatedboo0_
left outer join [Book] book1_ on relatedboo0_.RelatedBookId=book1_.Id
WHERE relatedboo0_.BookId=3
The WHERE statment should look something like this for thie particular case:
WHERE relatedboo0_.RelatedBookId = 3
SOLUTION
Here is what I had to do to get it working for all cases
Mapping:
HasManyToMany(x => x.RelatedBooks)
.ParentKeyColumn("BookId")
.ChildKeyColumn("RelatedBookId")
.Table("RelatedBooks");
Code:
var book = currentSession.Get<Book>(bookId);
if (book != null)
{
//Remove all of the Related Books
book.RelatedBooks.Clear();
//Get all other books that have this book as a related book
var booksWithRelated = currentSession.CreateCriteria<Book>()
.CreateAlias("RelatedBooks", "br")
.Add(Restrictions.Eq("br.Id", book.Id))
.List<Book>();
//Remove this book as a Related Book for all other Books
foreach (var tempBook in booksWithRelated)
{
tempBook.RelatedBooks.Remove(book);
tempBook.Save();
}
//Delete the book
book.Delete();
}
Rather than setting the cascade attribute, I think you need to simply empty the RelatedBooks collection before deleting a book.
book.RelatedBooks.Clear();
session.Delete(book);
Cascading deletes is not typically done in a many-to-many relationship because it will delete the object at the other end of the relationship, in this case a Book.
This just got updated:
http://fluentnhibernate.lighthouseapp.com/projects/33236/tickets/115-self-referencing-relationships