Mimicing Deferred Constraint with MySQL in Hibernate - java

As I understand from the MySQL documentation and from this question, it is NOT POSSIBLE to use deferred constraint in MySQL.
So, my question is; with below entity model (a uni-directional relation) in Hibernate, I want to swap two a collection of Package between two Product entities within a Transaction.
public class Product{
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(
name="product_selected_packages",
joinColumns = {#JoinColumn(name = "product")},
inverseJoinColumns = {#JoinColumn(name="packages",unique = true)},
uniqueConstraints = {#UniqueConstraint(name = "UK_package",columnNames = {"packages"})}
)
private List<Package> packages;
}
public class Package {
#Column(unique = true)
private String code;
}
But when I write this type of code below (this is all done within a #Transactional method),
List<Package> product1Packages = new ArrayList<>(product1.getPackages()); // save product1 packages into memory
product1.getPackages().clear(); // clear all product1 packages
session.flush(); // flush session in order to sync db with hibernate
product2.getPackages().addAll(product1Packages);
Of course there is a more complex logic behind it but in a general way, Hibernate produces below SQL(which makes sense)
START TRANSACTION;
DELETE FROM product_selected_packages WHERE product = :SOME_ID;
INSERT INTO product_selected_packages(product,packages) VALUES (1,1) , (1,2) , (1,3) ...;
ROLLBACK; -- OR COMMIT
But due to MySQL's limitations on constraints which is stated in the documentation; above statement throws an error.
Duplicate entry '1621' for key 'UK_unique_index_constraint'
Because documentation states;
MySQL checks foreign key constraints immediately; the check is not
deferred to transaction commit.
Long story short, how can I create a work-around method for this type of situation without losing the Transaction ?

Related

Hibernate Enver : #AuditJoinTable rows missing

Given an Entity that is audited by Envers, which contains one collection.
Entity A
#Audited
public class A{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
....
#OneToMany
#JoinColumn(name = "a_id")
#AuditJoinTable(name = "A_B_AUDIT"
,inverseJoinColumns = #JoinColumn(name = "a"))
private List<B> bs;
....
}
Entity B
#Audited
public class B{
#Id
private int id;
....
#Column(name = "a_id")
private int aId;
#ManyToOne
#JoinColumn(name = "a_id", insertable = false, updatable = false)
private A a;
}
As per their documentation
Envers stores the audit information for additions and deletions to these AuditJoinTables (A_B_AUDIT). But Unfortunately, this is not working and the rows inside the tables are missing.
When I run my project following tables gets created :
A_AUDIT
B_AUDIT
A_B_AUDIT
I've separate controllers to persist A object and B Object. When I try to save B with aId and A, audit_table (A_B_AUDIT) does not gets updated but B_AUDIT with updated revision gets updated.
Can someone please let me know what i am missing here.
Thank you !!
Hibernate Enver Version : 5.1.4.Final
As said in the comments, your issue is that you're performing the persistence of these two entities in separate transactions but you aren't making the proper association and thus you're tainting the state of Hibernate's first level cache. Inadvertently, you're also causing Envers not to be able to properly audit your entities because you're not giving it all the proper state.
In order for this use case to work with Envers, you need to modify how you are handling the persistence of your Entity B given that it is the only thing that seems to know about the relationship with Entity A.
During the persistence of Entity B, you should likely do this:
if ( b.getAId() != null ) {
A a = entityManager.find( A.class, b.getAId() );
a.getBs().add( b );
a = entityManager.merge( a );
b.setA( a );
entityManager.persist( b );
}
This means during the persistence of Entity B, you should get an audit row added in the audit join table like you expected and the state in the Hibernate first level cache will contain all the proper references between the two entities.
If we put Envers aside for a moment and focus on normal JPA provider usage, you should be able to do this after you persist B and it would work:
final A theA = b.getA();
assertNotNull( theA );
assertTrue( !theA.getBs().isEmpty() );
assertTrue( theA.getBs().contains( b ) );
Obviously if you aren't setting the associations, these assertions (which should all pass) won't. The only time they would pass would be when you requery entity B, but that should not be necessary.
Hopefully you understand.

JPA nativeQuery returns cached resultList

I have following classes:
Company.class:
public class Company {
#JoinTable(name = "company_employee", joinColumns = #JoinColumn(name = "company_id") , inverseJoinColumns = #JoinColumn(name = "employee_id") )
#ManyToMany(fetch = FetchType.LAZY)
private Set<Employee> employees;
#Column(name = "score")
private BigDecimal score;
}
and Employee.class
public class Employee {
#ManyToMany(fetch = FetchType.EAGER, mappedBy="employees")
private Set<Company> companies;
}
The Score column of Company is always null in the db and never updated via dao, because there is other table containing score for each unique pair Company-Employee.
I need the value of Score, only for the case when I fetch Employee by id, so this case all Company instances in the Set should contain score, thus I will get Employee-Company score pairs where employee is fetched Employee.
I have following code to achieve that:
public Employee get(Long id) {
Employee emp = (Employee) dao.find(id);
List<Company> compList = compnanyService.getByEmpId(id);
Set<Company> compSet = new HashSet<Company>(compList);
emp.setCompanies(compSet);
return emp;
}
And Company Dao contains method:
public List<Company> getByEmpId(Long id) {
final Query query = this.entityManager.createNativeQuery("select company.comp_id, ...some other fields, score.score from company join score on company.company_id=score.company_id where score.employee_id=:employee_id",
Company.class);
query.setParameter("employee_id", id);
List<Company> comps = query.getResultList();
return comps;
}
The problem is that getByEmpId(id) gives a ResultList where company.score is null though executed in the db it is not null.
I suspected that there is some caching intervening, so I tried to remove some columns from the native query, and it should have invoked an exception with "no column found" (or alike) message while mapping, but this method still gives List<Company> with all fields on their places though Hibernate prints out my native query in the console with all changes I make.
What am I doing wrong here and how to achieve what I need? Thank you.
It might be associated with first level cache, which can be out of sync when using native SQL queries. From here:
If you bypass JPA and execute DML directly on the database, either
through native SQL queries, JDBC, or JPQL UPDATE or DELETE queries,
then the database can be out of synch with the 1st level cache. If you
had accessed objects before executing the DML, they will have the old
state and not include the changes. Depending on what you are doing
this may be ok, otherwise you may want to refresh the affected objects
from the database.
So you can try using refresh method from EntityManager.
So I ended up doing that:
Created view in db from the query:
CREATE VIEW companyscore AS select company.comp_id, score.emp_id ...some other fields, score.score from company join score on company.comp_id=score.comp_id;
Created corresponding entity CompanyScore with composite primary id as comp_id and emp_id and created view as table.
Changed Employee entity to:
public class Employee {
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "emp_id")
private Set<CompanyScore> companies;
}
This way I not only have score field always consistent, but I can choose set of fields to show as the whole Company class is quite extensive and I don't need all the fields for this particular case.

HibernateException: collection is not associated with any session for bigger result set

I have application that is using JPA 2.0 and Hibernate as the persistence provider. I am using EntityManager to build my queries.
I am expericing the classic org.hibernate.HibernateException: collection is not associated with any session error on the big set of data. When I am trying to extract just a few Datapoints it is working correctly, but once I am extracting 30+ results I am getting org.hibernate.HibernateException: collection is not associated with any session error. I thought I should not get it since I am using fetch = FetchType.EAGER.
Any help will be greatly appreciated!
Here are two of my entities.
DatapointView:
#Entity
#Table(name = "DATAPOINT_VIEW")
public class DatapointView implements Serializable {
...many fields...
#OneToMany(fetch = FetchType.EAGER )
#JoinColumn(name = "EXPERIMENT_ID", referencedColumnName = "EXPERIMENT_ID")
private List<ExperimentViewEntity> experiments= new ArrayList<ExperimentViewEntity>();
...
}
ExperimentViewEntity:
#Entity
#javax.persistence.Table(name = "EXPERIMENT_VIEW")
public class ExperimentViewEntity {
...many fields...
#Column(name = "EXPERIMENT_ID", nullable = false, insertable = true, updatable = true, length = 36)
private String experimentId;
...
}
I think you don't have a still open transaction when interaction with the DatapointView object. Eager Fetching is probably not a solution for that. Also the hibernate documentation say you should not do it in that way please see the documentation for OneToMany -> http://docs.jboss.org/hibernate/annotations/3.5/reference/en/html_single/#entity-mapping-association
When you expect to have a large amount of data in that list you should consider move the relation to the ExperimentViewEntity (unidirectional). And query for the ExperimentViewEntities when needed. This does also allow to do pagination, which may be necessary in terms of performance.

failed to lazily initialize a collection of role: could not initialize proxy - no Session error in #ManyToMany mappin annotation in Hibernate? [duplicate]

This question already has answers here:
Difference between FetchType LAZY and EAGER in Java Persistence API?
(18 answers)
Closed 7 years ago.
Hi am new to java server side to create JSON API, am using ManytoMany mapping in hibernate to join the two tables.I have two classes one is Product.class and Offers.class.
Product.class
#Entity
#Table(name = "products")
public class Product {
#Column(name = "merchant_code")
private String merchant_code;
#Column(name = "branch_code")
private String branch_code;
#Column(name = "product_category_code")
private String product_category_code;
#Id #GeneratedValue
#Column(name = "product_code")
private String product_code;
#Column(name = "product_short_desc")
private String product_short_desc;
#Column(name = "product_long_desc")
private String product_long_desc;
#Column(name = "image")
private String image;
#Column(name = "price")
private String price;
#Column(name = "Active_Inactive")
private String Active_Inactive;
#ManyToMany(mappedBy = "offer_relation_code", fetch = FetchType.EAGER)
#Where(clause = "offer_type_code = 1")
private List<Offers> offer;
//here my getter setter
}
Offers.class
#Entity
#Table(name = "offers")
public class Offers {
#Id #GeneratedValue
#Column(name = "offer_code")
private int offer_code;
#Column(name = "offer_type_code")
private int offer_type_code;
#Column(name = "offer_relation_code")
private int offer_relation_code;
#Column(name = "branch_code")
private int branch_code;
#Column(name = "valid_from")
private String valid_from;
#Column(name = "valid_until")
private String valid_until;
#Column(name = "offer_value")
private int offer_value;
#Column(name = "offer_desc")
private String offer_desc;
//here my getter setter
}
To fetch data
factory = cfg.configure().addAnnotatedClass(Product.class).buildSessionFactory(registry);
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Criteria criteria = session.createCriteria(Product.class);
criteria.setFetchMode("product",FetchMode.JOIN);
Criterion merchant_code_Criterion = Restrictions.eq("merchant_code", new String(merchant_code));
Criterion branch_code_Criterion = Restrictions.eq("branch_code", new String(branch_code));
LogicalExpression andExp = Restrictions.and(merchant_code_Criterion,branch_code_Criterion);
criteria.add(andExp);
search_products = (ArrayList<Product>) criteria.list();
tx.commit();
} catch (HibernateException e) {
// TODO: handle exception
if (tx != null)
tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
Am join the offer table with product table like #ManyToMany(mappedBy = "offer_relation_code", fetch = FetchType.EAGER) am search it in Google ,many of them said don't use EAGER, it leads to some issue, but when i am using #ManyToMany(mappedBy = "offer_relation_code", fetch = FetchType.LAZY) is shows error like failed to lazily initialize a collection of role: could not initialize proxy - no Session. When am using EAGER its working fine without error.Using EAGER is good or bad.Can any one Explain.
Both EAGER and LAZY have use cases when they are useful, they are not good or bad generally speaking.
When some relationship is marked as EAGER it means all of the data from that relation will be fetched from the database when the parent entity is fetched. One SQL will be used for all data.
With LAZY relationship, only the parent entity's data is fetched initially. The lazy relation is replaced with Hibernate's proxy class, which will fetch the child entity's data on first access to any of its properties, using a separate SQL statement. However, there has to be an active Hibernate session in order for this to work. When called outside of active session, you get the exception failed to lazily initialize a collection.
For #nToMany mappings, LAZY is the default which makes perfect sense because Many can really mean many, and there is a good chance you don't need all of the mapped data. So, it's generally a good idea to leave that at LAZY and fetch the data in services where needed. Hibernate has a utility method for initializing lazy relations, Hibernate.initialize(parent.getLazyChild()).
But, as I said in the beginning, it all depends on the use case and it's best if you know all the implications of EAGER and LAZY so you can make your own decision.
Let's first talk about the two POJO's you have.
Offer class represent one table. In other words, it represents all the columns from just one table, with no reference to any other other table.
Product class also represents only one table, but then you have a reference to Offer class.
Now, if you would like to get records for product code 'abc'
EAGER: When using this, you are asking JPA to populate the POJO with data from product and also the corresponding data from offer table.
LAZY: When using this, you are asking JPA to populate POJO with data only from product table. Only when you call, getOffers() then another database call should be made to populate corresponding data from offer table.
You can use LAZY, when the data from a referenced table is not required that often. There is a good probability (>25%) that Offer data may never be shown.
You should use EAGER, when the data from a referenced table,is almost always, required.
For your error, in your final block you are calling session.close(). When you are using LAZY, the transaction is closed after initial fetch. And when you call getOffers(), JPA tries to make a db connection - but fails as it's using already closed connection/session.
EAGER tells hibernate to always fetch all the components on the Many relation (all the Offers in your question), even in situations when you don't need them.
With LAZY, you are responsible to get those Offers inside the transaction, when you really need them.

How can I cascade delete a collection which is part of a JPA entity?

#Entity
public class Report extends Model {
public Date date;
public double availability;
#ElementCollection
#Cascade(value={CascadeType.ALL})
public Map<FaultCategory, Integer> categories;
}
In one of my jobs I have the following code:
int n = MonthlyReport.delete("date = ?", date);
This always fails to delete the entity with the following error:
The DELETE statement conflicted with the REFERENCE constraint "FK966F0D9A66DB1E54". The conflict occurred in database "TFADB", table "dbo.MonthlyReport_categories", column 'MonthlyReport_id'.
How can I specify the mapping so the elements from the categories collection get deleted when the report is deleted?
Cascading delete (and cascading operations in general) is effective only when operation is done via EntityManager. Not when delete is done as bulk delete via JP QL /HQL query. You cannot specify mapping that would chain removal to the elements in ElementCollection when removal is done via query.
ElementCollection annotation does not have cascade attribute, because operations are always cascaded. When you remove your entity via EntityManager.remove(), operation is cascaded to the ElementCollection.
You have to fetch all MonthlyReport entities you want to delete and call EntityManager.remove for each of them. Looks like instead of this in Play framework you can also call delete-method in entity.
The answer provided by J.T. is correct, but was incomplete for me and for sebge2 as pointed out in his/her comment.
The combination of #ElementCollection and #OnDelete further requires #JoinColumn().
Follow-up example:
#Entity
public class Report extends Model {
#Id
#Column(name = "report_id", columnDefinition = "BINARY(16)")
public UUID id; // Added for the sake of this entity having a primary key
public Date date;
public double availability;
#ElementCollection
#CollectionTable(name = "report_category", joinColumns = #JoinColumn(name = "report_id")) // choose the name of the DB table storing the Map<>
#MapKeyColumn(name = "fault_category_key") // choose the name of the DB column used to store the Map<> key
#Column(name = "fault_category_value") // choose the name of the DB column used to store the Map<> value
#JoinColumn(name = "report_id") // name of the #Id column of this entity
#OnDelete(action = OnDeleteAction.CASCADE)
#Cascade(value={CascadeType.ALL})
public Map<FaultCategory, Integer> categories;
}
This setup will create a table called report and another table report_category with three columns: report_id, fault_category_key, fault_category_value. The foreign key constraint between report_category.report_id and report.report_id will be ON DELETE CASCADE. I tested this setup with Map<String, String>.
We found the magic ticket! Add OnDelete(action= OnDeleteAction.CASCADE) to the ElementCollection. This allows us to remove the item from SQL (outside of the entityManager).
I met the same problem, and here is my code sample.
#ElementCollection
#CollectionTable(name = "table_tag", joinColumns=#JoinColumn(name = "data_id"))
#MapKeyColumn(name = "name")
#Column(name = "content")
private Map<String, String> tags
After a lot of tries, finally, I just add foreign key constraint for the table_tag.data_id to the parent table's primary key. Notice that you should set ON DELETE CASCADE to the constraint.
You can delete the parent entity by any ways, and the child element collection would be deleted too.
As an alternative to the hibernate-specific annotation #org.hibernate.annotations.OnDelete, you can also provide the constraint via #javax.persistence.ForeignKey to customize automatic schema generation:
#CollectionTable(name = "foo_bar", foreignKey = #ForeignKey(
name = "fk_foo_bar",
foreignKeyDefinition = "foreign key (foo_id) references Foo (id) on delete cascade"))
private List<String> bar;

Categories