I have two entities:
#Entity
public class CustomerMainInformation {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#Column(unique = true, length = 10)
private String customerNumber;
#OneToMany(mappedBy = "firstCustomerRelationship", cascade = CascadeType.ALL)
private List<CustomerRelationship> firstCustomerRelationship;
#OneToMany(mappedBy = "secondCustomerRelationship", cascade = CascadeType.ALL)
private List<CustomerRelationship> secondCustomerRelationship;
// setter & getter
}
#Entity
public class CustomerRelationship {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
#JoinColumn(columnDefinition = "first")
private CustomerMainInformation firstCustomerRelationship;
#ManyToOne
#JoinColumn(columnDefinition = "second")
private CustomerMainInformation secondCustomerRelationship;
// setter & getter
}
I executed following code:
CustomerMainInformation customerMainInformation =manager.getReference(CustomerMainInformation.class, 1L);
System.out.println(customerMainInformation.getFirstCustomerRelationship().size());
System.out.println(customerMainInformation.getSecondCustomerRelationship().size());
CustomerMainInformation customerMainInformation2 = manager.getReference(customerMainInformation.getClass(), 2L);
CustomerRelationship customerRelationship = new CustomerRelationship();
customerRelationship.setFirstCustomerRelationship(customerMainInformation2);
customerRelationship.setSecondCustomerRelationship(customerMainInformation);
manager.persist(customerRelationship);
transaction.commit();
EntityManager manager2 = factory.createEntityManager();
CustomerMainInformation customerMainInformation3 = manager2.getReference(CustomerMainInformation.class, 1L);
System.out.println(customerMainInformation3.getFirstCustomerRelationship().size());
System.out.println(customerMainInformation3.getSecondCustomerRelationship().size());
In this code relationship size increment but because .getSecondCustomerRelationship() before called, list size not changed.
If #Cacheable(false) add to CustomerRelationship the .size() return correct list size.
You don't seem to be maintaining both sides of your relationships in the code shown. Calling setSecondCustomerRelationship Will set one side of it, and this side controls the foreign key field in the db. But your java object model is out of sync with your changes unless you also add the customerRelationship to customerMainInformation's collection. JPA does not maintain your relationships for you, so if you change one side, you are responsible for keeping the other side insync.
You can either set both sides, or force the customerMainInformation to be refreshed from the database after you commit. The first option is far more efficient.
Related
I have Car and Color classes. Car class has both Entity and Id fields of Color.
Here is the Car class code:
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(name = "color_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = CarColor.class, fetch = FetchType.EAGER)
private CarColor color;
#Column(name = "color_id")
private Long colorId;
}
Here is the Color class code:
public class CarColor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#JsonIgnore
#OneToMany(mappedBy = "color")
List<Car> cars;
}
When I'm fetching a Car i have an extra query to get a color Entity which is causing n+1 problem. So I have tried to use joins.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor)
.where(predicate)
.fetch();
I'm getting these queries:
select
car0_.id as id1_3_,
car0_.color_id as color_id3_3_,
from
car car0_
inner join
color carcolor2_
on car0_.color_id=carcolor2_.id
Hibernate:
select
carcolor0_.id as id1_6_0_,
carcolor0_.name as name2_6_0_
from
color carcolor0_
where
carcolor0_.id=?
As you can see, at the end I have an extra query to get color even though it has already joined Color entity. That is the main problem. Maybe it somehow connected with my decision to store both Entity and Id.
Queries don't use the association eagerness to add joins arbitrarily to your query. After that the entity loader still notices that this is an eager association and will fetch it separately.
You can however simply add the join yourself. Important here is to add the join as fetch join, so that the join result will be initialised as the value of the association.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor).fetchJoin()
.where(predicate)
.fetch();
I have 2 entities:
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_company")
public class SourceCompany {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#OneToMany( mappedBy = "company")
private final Set<SourceUser> users = new HashSet<>();
#Column(name = "version")
#Version
private Long version;
}
#Data
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Table(name = "source_user")
public class SourceUser {
#Id
#EqualsAndHashCode.Include
private UUID id;
private String name;
#Column(name = "version")
#Version
private Long version;
//ref
#ManyToOne
#JoinColumn(name = "fk_source_company")
private SourceCompany company;
}
Is it correct to save in this way (only 2 save)?
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
or I need to save the user (without company) and the company (without user) and after that to update the user with a save and the company (without save because is not the owner) like this (3 save):
#Test
public void testSourceUserSave() {
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUserRepository.save(sourceUser);
sourceCompanyRepository.save(sourceCompany);
sourceUser.setCompany(sourceCompany);
sourceCompany.getUsers().add(sourceUser);
sourceUserRepository.save(sourceUser);
assertNotNull(sourceUser);
assertEquals(sourceUser.getCompany().getId(), sourceCompany.getId());
assertEquals(sourceCompany.getUsers().stream().findFirst().get().getId(), sourceUser.getId());
}
It seems, looking in the db, that the first way works, so in future can I update only the owner side (I mean update and save) and so can I update the not-owner side only in the object without save it again?
Thanks in advance
You usually tend to save only one of the objects. This can be done adding the
#ManyToOne(cascade = CascadeType.PERSIST)
to the mapping annotation. This makes sure that the nested entities get persisted too
You would need to do just:
SourceCompany sourceCompany= new SourceCompany();
sourceCompany.setName("xxx");
sourceCompany.setId(UUID.fromString("2bf05cbc-d530-11eb-b8bc-0242ac130003"));
SourceUser sourceUser= new SourceUser();
sourceUser.setName("dev-team");
sourceUser.setId(UUID.fromString("4bede7a0-d530-11eb-b8bc-0242ac130003"));
sourceUser.setCompany(sourceCompany);
sourceUserRepository.save(sourceUser);
One more thing to note is that the .save method actually returns an entity itself. That entity is the persisted entity just created. Basically if you manage everything within a single transactional method any modification to the persisted entity within that method (transaction) will be applied without calling any save, merge or update method
I suggest reading about the #Transactional annotation
I have tow classes, the "Article" which contains a #ManyToOne reference to a "SurchargeGroup" which specifies the surcharge for that article.
#Entity
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public final class Article {
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Getter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.ARTICLE_ID_WRITE)})
private int id;
#JoinColumn(nullable = false)
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup surchargeGroup;
}
The other class "SurchargeGroup" contains a parent object reference which can inherit the surcharge to the "SurchargeGroup" if it isn't set the case that no surcharge is provided by any parent is not possible.
#Table
#Entity
#EqualsAndHashCode(doNotUseGetters = true)
#Audited
public class SurchargeGroup implements Serializable, Cloneable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_ID_WRITE)})
private int id;
#Column
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SURCHARGE_WRITE)})
private Double surcharge;
#Column
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_NAME_WRITE)})
private String name;
#JoinColumn
#ManyToOne(fetch = FetchType.EAGER)
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private SurchargeGroup parent;
public double getSurcharge() {
if (surcharge == null) {
return parent == null
? supplier == null
? Setting.SURCHARGE_DEFAULT.getDoubleValue()
: supplier.getDefaultSurcharge()
: parent.getSurcharge();
} else return surcharge;
}
#JoinColumn
#ManyToOne
#Getter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_READ)})
#Setter(onMethod_ = {#Key(PermissionKey.SURCHARGE_TABLE_SUPPLIER_WRITE)})
private Supplier supplier;
}
My problem is now that if I call the "getSurcharge()" method I get this exception which I cannot explain to myself because I marked the surcharge group to fetch eager
Exception in thread "AWT-EventQueue-0" org.hibernate.LazyInitializationException: could not initialize proxy [kernbeisser.DBEntities.SurchargeGroup#1046] - the owning Session was closed
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:172)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:309)
at org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor.intercept(ByteBuddyInterceptor.java:45)
at org.hibernate.proxy.ProxyConfiguration$InterceptorDispatcher.intercept(ProxyConfiguration.java:95)
at kernbeisser.DBEntities.SurchargeGroup$HibernateProxy$cdTAuBkS.getSurcharge(Unknown Source)
I asked myself if this could get caused by the #Audited annotation? Any ideas? Thanks a lot!
Note: the #Key annotations have no effect to this scenario.
Here is what the debugger shows (Sorry for the German toString() functions):
Hibernate needs to stop eagerly fetching associations at some point, otherwise it would need to join an infinite number of times the SurchargeGroup entity (since it references itself).
The depth these fetches can be controlled application wide using the hibernate.max_fetch_depth property.
The source of the error was the AuditReader it doesn't fetch all eager properties even if they are annotated as Fetch.EAGER
It looks like the AuditReader only fetches one level of eager relations:
Article -> SurchargeGroup -> SurchargeGroup -> ...
(fetched) (fetched) (not fetched)
I am currently stuck on an issue of updating a parent entity field and getting the update to cascade to all its children's fields.
Here is an example of what I am trying to accomplish:
User.java
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, columnDefinition = "TINYINT(1) default false")
private Boolean archived = Boolean.FALSE;
#OneToMany(mappedBy = "user")
private Set<Invoice> invoices = new HashSet<Invoice>();
// Setters & Getters
}
Invoice.java
#Entity
public class Invoice{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, columnDefinition = "TINYINT(1) default false")
private Boolean archived = Boolean.FALSE;
#ManyToOne(mappedBy = "invoices")
#JoinColumn(nullable = false)
private User user;
// Setters & Getters
}
When I update the archived value to true. I want all the invoices to also be updated to true as well.
I.E
public Boolean archiveUserById(Integer id) {
User user= entity_manager.find(User.class, id);
Boolean result = false;
if(auction != null) {
// This should cascade to all the invoices as well and update their archived fields to true as well
user.setArchived(true);
try {
entity_manager.getTransaction().begin();
entity_manager.merge(auction);
entity_manager.getTransaction().commit();
result = true;
} catch(Exception e) {
e.printStackTrace();
}
}
return result;
}
I've tried using cascade = CascadeType.PERSIST and #JoinTable(....) with all the referenced columns, but they are still failing to update the fields correctly.
To clarify is there a way to update a child's field through its parents' update with a Cascade effect?
Thank-you for the help.
EDIT
To clarify my question, I am trying to add a constraint cascade effect when a field on the parent entity is updated to reflect on the child entity's same field. I am trying to avoid any logic within the Entity itself. Is there a way to do this through annotations only?
Something to the same effect as this:
ALTER TABLE `child` ADD CONSTRAINT `childs-archived-mirrors-parent`
FOREIGN KEY (`archived`, `parentId`)
REFERENCES `parent`(`archived`, `id`)
ON DELETE RESTRICT ON UPDATE CASCADE;
try adding #PreUpdate method on parent where you manually change children inside the list.
Thanks for all your help.
I ended up finding a solution. I created a custom foreign key definition that will cascade any update on the archived field to its child entities.
Below is how I accomplished that.
#Entity
public class Invoice{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, columnDefinition = "TINYINT(1) default false")
private Boolean archived = Boolean.FALSE;
#ManyToOne(mappedBy = "invoices")
#JoinColumn(nullable = false, foreignKey = #ForeignKey(foreignKeyDefinition = "FOREIGN KEY (archived, user_id) REFERENCES user(archived, id) ON DELETE RESTRICT ON UPDATE CASCADE"))
private User user;
// Setters & Getters
}
I am working on a web app and I am using JSF and JPA(EclipseLink). I have the tables story and story_translate, which are mapped as follows:
#Entity
#Table(name = "story")
public class Story{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
private String title;
private String description;
#OneToMany(mappedBy = "story", cascade=CascadeType.ALL)
private List<StoryTranslate> translateList;
//getters and setters
}
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#Column(name="STORY_ID")
private Integer storyId;
#ManyToOne
#JoinColumn(name="story_id", referencedColumnName="id", updatable=false, insertable=false)
private Story story;
//some other fields, getters and setters
}
In a ManagedBean I am doing the following:
StoryTranslate translate = new StoryTranslate(null, sessionController.getEntity().getId(), getAuthUser().getId(), language,
title, description, new Date(), false);
EntityTransaction transaction = TransactionSingleton.getActiveInstance();
Story story = storyService.read(sessionController.getEntity().getId());
if (story != null){
if (story.getTranslateList() == null){
story.setTranslateList(new ArrayList<StoryTranslate>());
}
story.getTranslateList().add(translate);
translate.setStory(story);
}
transaction.commit();
When I try to create a new StoryTranslate, I get a DatabaseException, saying the story_id cannot be null.
I have managed relationships before, but I have never seen this error.
Where is the problem?
EDIT: I am sorry, but I have forgotten about another part of the mapping(must be the lack of sleep).
The problem is that your declare the storyId property in the StoryTranslate class for the STORY_ID column but when adding a new StoryTranslate , you do not set any value to its storyId property and I believe STORY_ID column has a NOT NULL constraint and that why you get the exception saying that story_id cannot be null.
The problem should be fixed once you set the storyId property of the StoryTranslate instance before committing the transaction .
But it is strange that you map the STORY_ID column to two different properties ( storyId and story) of the StoryTranslate class . Actually you do not need to declare storyId property as this value can be retrieved from the story instance . I suggest you change the mapping of StoryTranslate to the following and your code should work fine without any changes.
#Entity
#Table(name = "story_translate")
public class StoryTranslate{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#ManyToOne
#JoinColumn(name="story_id")
private Story story;
//some other fields, getters and setters
}