Hibernate reloading entities in a fetch join - java

I am having a problem with Hibernate reloading the entities in a query even though they are being fetched as part of the main query.
The entities are as follows (simplified)
class Data {
#Id
String guid;
#ManyToOne(fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
DataContents contents;
}
class DataClosure {
#Id
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ancestor_id", nullable = false)
private Data ancestor;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "descendant_id", nullable = false)
private Data descendant;
private int length;
}
This is modelling a closure table of parent / child relationships.
I have set up some criteria as follows
final Criteria criteria = getSession()
.createCriteria(DataClosure.class, "dc");
criteria.createAlias("dc", "a");
criteria.createAlias("dc.descendant", "d");
criteria.setFetchMode("a", FetchMode.JOIN);
criteria.setFetchMode("d", FetchMode.JOIN);
criteria.add(Restrictions.eq("d.metadataGuid",guidParameter));
criteria.add(Restrictions.ne("a.metadataGuid",guidParameter));
This results in the following SQL query
select
this_.descendant_id as descenda2_21_2_,
this_.ancestor_id as ancestor3_21_2_,
this_.length as length1_21_2_,
d2_.guid as metadata1_20_0_,
d2_.name as name5_20_0_,
a1_.guid as metadata1_20_1_,
a1_.name as name6_20_1_
from
data_closure this_
inner join
data d2_
on this_.descendant_id=d2_.metadata_guid
inner join
data a1_
on this_.ancestor_id=a1_.metadata_guid
where
d2_.guid=?
and a1_.guid<>?
which looks like it is correctly implementing the join fetch. However when I execute
List list = criteria.list();
I see one of these entries in the SQL log per row in the result set
Result set row: 0
DEBUG Loader - Loading entity: [Data#testGuid19]
DEBUG SQL -
select
data0_.guid as guid1_20_0_,
data0_.title as title5_20_0_,
from
data data0_
where
data0_.guid=?
Hibernate:
(omitted)
DEBUG Loader - Result set row: 0
DEBUG Loader - Result row: EntityKey[Data#testGuid19]
DEBUG TwoPhaseLoad - Resolving associations for [Data#testGuid19]
DEBUG Loader - Loading entity: [DataContents#7F1134F890A446BBB47F3EB64C1CF668]
DEBUG SQL -
select
dataContents0_.guid as guid_12_0_,
dataContents0_.isoCreationDate as isoCreat2_12_0_,
from
dataContents dataContents0_
where
dataContents0_.guid=?
Hibernate:
(omitted)
It is looks like even though the DataContents is marked as lazily loaded, it's being loaded eagerly.
So I either want some way in my query to fetch join DataClosure and Data and lazily fetch DataContents, or to fetch join the DataContents if that is not possible.
Edit:
Modelling the closure table like this
class DataClosure {
#Id
#Column(name = "ancestor_id", nullable = false, length =36 )
private String ancestorId;
#Id
#Column(name = "descendant_id", nullable = false, length =36 )
private String descendantId;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "ancestor_id", nullable = false)
private Data ancestor;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
#JoinColumn(name = "descendant_id", nullable = false)
private Data descendant;
private int length;
}
fixed the problem. In other words, having #Id annotation on entities from other tables seemed to cause the issue, even though there was nothing wrong with the queries generated.

I think your problem here might be this
#NotFound(action = NotFoundAction.IGNORE)
There are plenty of google results where using that causes the lazy loading to become eager. I think it is a bug in Hibernate.
Adding this to the list of annotations should fix the problem
#LazyToOne(value=LazyToOneOption.NO_PROXY)
Since that informs Hibernate that you will not try to use that property later on so no proxy is required.

Related

Why jpa manyToOne lazy fetch is unavailable, it still lead to useless Association query

#JsonIgnoreProperties({"bmsPost"})
#ManyToOne(optional = true,fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
private UmsUser umsUser;
#JsonIgnoreProperties({"umsUser"})
#Column(name = "create_time", nullable = false)
private Date createTime;
#OneToMany(mappedBy = "umsUser",cascade=CascadeType.ALL,fetch=FetchType.LAZY)
// #JoinColumn(name="user_id",referencedColumnName="id")
private List<BmsPost> bmsPost;
I just want to select BmsPost, but it selects BmsPost and UmsUser
List<BmsPost> findAllByTitle(String title);
Result:
Hibernate: select bmspost0_.id as id1_1_, bmspost0_.collects as collects2_1_, bmspost0_.comments as comments3_1_, bmspost0_.content as content4_1_, bmspost0_.create_time as create_t5_1_, bmspost0_.essence as essence6_1_, bmspost0_.modify_time as modify_t7_1_, bmspost0_.section_id as section_8_1_, bmspost0_.title as title9_1_, bmspost0_.top as top10_1_, bmspost0_.user_id as user_id11_1_, bmspost0_.view as view12_1_ from bms_post bmspost0_ where bmspost0_.title=?
Hibernate: select umsuser0_.id as id1_3_0_, umsuser0_.active as active2_3_0_, umsuser0_.alias as alias3_3_0_, umsuser0_.avatar as avatar4_3_0_, umsuser0_.bio as bio5_3_0_, umsuser0_.create_time as create_t6_3_0_, umsuser0_.email as email7_3_0_, umsuser0_.mobile as mobile8_3_0_, umsuser0_.modify_time as modify_t9_3_0_, umsuser0_.password as passwor10_3_0_, umsuser0_.role_id as role_id11_3_0_, umsuser0_.score as score12_3_0_, umsuser0_.status as status13_3_0_, umsuser0_.token as token14_3_0_, umsuser0_.username as usernam15_3_0_ from ums_user umsuser0_ where umsuser0_.id=?
[BmsPost{id='1408691924814835713', title='ds', content='dsd', userId='1349618748226658305', comments=0, collects=0, view=0,top=false, essence=false, sectionId=0, createTime=2021-06-26 15:41:42.0,modifyTime=null, umsUser=com.example.demo.Entity.UmsUser#38d115a4}]
How to avoid it?
Do you define List bmsPost in your DTO?
if you define that property in your DTO when you get your Data from DB Hibernate create a query for them to get data it needs

Hibernate - join ElementCollection with the rest of an entity fetch query

I have major performance issues when I try to map an entity into a response.
This is the entity:
#Entity
#Table(name = "MyEntity")
public class MyEntity extends BaseEntity {
#Column(name = "someOtherId", nullable = false)
private String someOtherId;
#ElementCollection
#CollectionTable(name = "Phones", joinColumns = #JoinColumn(name = "myEntityId"))
#Column(name = "phone")
private List<String> phones; // <------- we care about this
#ElementCollection
#CollectionTable(name = "Websites", joinColumns = #JoinColumn(name = "myEntityId"))
#Column(name = "websites")
private List<String> websites; // <------- we care about this
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "myEntity")
private List<ContactEntity> bbb;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "myEntity")
private List<AddressEntity> ccc;
}
This is how I use the DAL to fetch it:
List<MyEntity> findByTenantIdAndIdIn(String someOtherId, Set<String> MyEntityIds);
Now when I iterate over List<MyEntity> to map it, and call myEntity.getPhones(), I see that a DB call is being made, which is what causes the slowdown, a 70 seconds slowdown on 1000 entities.
So what can I do to force it to join in the first query it did when I called findByTenantIdAndIdIn?
Notes:
Phones is a simple table with columns: [myEntityId, phone]
The same problem happens with Websites
This has nothing to do with it being an #ElementCollection. Like you figured out, you can use subselect fetching or could also use a batch size for select fetching(the default strategy). Another possibility is to use a fetch join in the query, but be careful when fetch joining multiple collections as that might create a cartesian product which leads to a performance problem cause by too many rows being transfered. A fetch join example HQL query looks like this:
SELECT e FROM MyEntity e LEFT JOIN FETCH e.phones LEFT JOIN FETCH e.websites
I solved it right after I posted this.
I annotated phones and websites with #Fetch(FetchMode.SUBSELECT) which creates a parallel subquery.
Another way to solve this is simply to not use #ElementCollection because it has bad performance, use an Entity instead as they recommend in the video here: https://thorben-janssen.com/hibernate-tips-query-elementcollection/

Hibernate OneToOne between PK's with lazy behaviour

I'm trying to achieve to have an entity called MyEntity along with another entity called MyEntityInfo using Hibernate 5.3.13.Final with annotations under Wildfly 18.
The idea is to have MyEntity store some commonly requested fields, and MyEntityInfo store some rarely requested fields. Both share the same primary key called SID (Long), and there is a FK from Info's SID to Entity's SID. There can be entities without info.
Normally you will not require the additional info. For example, I don't want the info entity to be fetched when I query my entity like this:
MyEntityImpl entity = em.find(MyEntityImpl.class, 1L);
However, when I run this code, I find that there's a second query, fetching the Info entity along the main one, as in an EAGER behaviour.
I'm mapping the relationship using #OneToOne. I've tried several combinations of FetchType, optional and #LazyToOne, but so far without success.
Here is the code for both MyEntity and MyEntityInfo classes (additional getters and setters removed):
MyEntity (ID generator is a custom sequence generator):
#Entity
#Table(name = MyEntityImpl.TABLE_NAME)
public class MyEntityImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY";
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "GEN_" +
TABLE_NAME)
#GenericGenerator(name = "GEN_" +
TABLE_NAME, strategy = CoreIdGenerator.ID_GENERATOR, parameters = {
#Parameter(name = "tableName", value = TABLE_NAME) })
#Column(name = "sid", nullable = false, unique = true)
private Long sid;
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = true)
#LazyToOne(LazyToOneOption.NO_PROXY)
private MyEntityInfoImpl info;
#Column
private String field;
MyEntityInfo:
#Entity
#Table(name = MyEntityInfoImpl.TABLE_NAME)
public class MyEntityInfoImpl {
public static final String TABLE_NAME = "TMP_MY_ENTITY_INFO";
#Id
#Column(name = "SID", nullable = false, unique = true)
private Long sid;
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;
#Column(name = "INFO_FIELD")
private String infoField;
I've tried this solution, but as I said, it didn't work for me:
Hibernate lazy loading for reverse one to one workaround - how does this work?
I've managed to do something somewhat similar using #OneToMany and managing data manually, but that's not what I'd like to do. However, another alternatives and information on whether this can be achieved or not using #OneToOne, or the right design pattern to do this are also welcome.
PS: Database tables creation for SQL Server, in case you want to try it:
create table TMP_MY_ENTITY (SID NUMERIC(19,0) NOT NULL, FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY ADD CONSTRAINT PK_TMP_MY_ENTITY PRIMARY KEY CLUSTERED (SID);
go
create table TMP_MY_ENTITY_INFO (SID NUMERIC(19,0) NOT NULL, INFO_FIELD VARCHAR(100));
go
ALTER TABLE TMP_MY_ENTITY_INFO ADD CONSTRAINT PK_TMP_MY_ENTITY_INFO PRIMARY KEY CLUSTERED (SID);
go
CREATE SEQUENCE SEQ_TMP_MY_ENTITY START WITH 1 INCREMENT BY 1 MINVALUE 1 CACHE 20;
alter table TMP_MY_ENTITY_INFO add constraint FK_TMP_MY_ENT_INFO_MY_ENT FOREIGN KEY (SID) references TMP_MY_ENTITY(SID);
go
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 1');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 1');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 2');
insert into TMP_MY_ENTITY_INFO(SID, INFO_FIELD) VALUES ((SELECT MAX(SID) FROM TMP_MY_ENTITY), 'Info 2');
insert into TMP_MY_ENTITY(SID, FIELD) VALUES (NEXT VALUE FOR SEQ_TMP_MY_ENTITY, 'Field 3 no info');
-- DELETE ALL
drop table TMP_MY_ENTITY_INFO;
drop table TMP_MY_ENTITY;
drop sequence SEQ_TMP_MY_ENTITY;
After following #SternK link, and upgrading to Wildfly 19 and Hibernate 5.4.14, it finally worked by using #MapsId.
The right mapping to use is this:
MyEntity:
public class MyEntityImpl {
#OneToOne(mappedBy = "myEntity", cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "SID")
private MyEntityInfoImpl info;
MyEntityInfo:
public class MyEntityInfoImpl {
#OneToOne(fetch = FetchType.EAGER, optional = false)
#MapsId
#JoinColumn(name = "SID", referencedColumnName = "SID", insertable = false, updatable = false, nullable = false)
private MyEntityImpl myEntity;

Create referenced entity if null with JPA/Hibernate

I have this class:
public class Tenant {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#NaturalId
#Column(name = "name", nullable = false, updatable = false, unique = true)
private String name;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, orphanRemoval = true)
private List<User> users;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Role> roles;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL, orphanRemoval = true, optional = false)
private TenantLimits limits;
}
Where of course all referenced classes are entities. I'm able to create, update and retrieve everything from here, but since private TenantLimits limits; refers to an entity created after Tenant was created many of my Tenants elements don't contains any matched TenantLimits.
So my question is: How can I insert in the database a value in TenantLimits if is null when I'm going to retrieve Tenant? In Java I can easily check (of course) if the property is null and insert manually foreach retrieve, but since the retrieve of this entity is present in different places in my code I'd to have something that manage this automatically if exists
You are telling Hibernate that Tenant.limits cannot be null by mapping it with "optional=false". It will 100% adhere to this definition. It will only create valid tenants and I assume it will throw you exceptions if the state of the database is invalid. It won't let you fix your data.
You should fix the state of your database by any other means than with this particular Hibernate mapping.
You might have to migrate in 2 steps. Let's say, make the mapping "optional=true". Then you can run a Java process to fix your data (maybe by using an entity listener). Then - change it back to "optional=false".

fetch data in ManyToOne relation using Restriction

There are two tables with #OneToMany and #ManyToOne bidirectional relation, like this:
#Entity
public class Asset {
private int id;
private int count;
#OneToMany
private Set<Dealing> dealings;
...
}
#Entity
public class Dealing {
private int id;
...
#ManyToOne
#JoinColumn(name = "customer_id", nullable = false, updatable = false)
private Customer customer;
#ManyToOne
#JoinColumn(name = "product_id", nullable = false, updatable = false)
private Product product;
#ManyToOne(cascade = CascadeType.ALL)
private Asset asset;
}
all things sound OK, but when I want to search data using Restriction like this,
session.createCriteria(Asset.class).add(Restrictions.eq("dealings.customer.id", customerId)).add(Restrictions.eq("dealing.product.id", productId)).list();
In this level I get this error,
could not resolve property: dealings.customer of: com.project.foo.model.Asset
one of the solutions are to change my strategy but i wasted time to find this,btw I don't have any idea about it, do you ?
First of all, you don't have a bidirectional OneToMany association, but two unrelated unidirectional associations. In a bidirectional OneToMany association the One side must be marked as the inverse of the Many side using the mappedBy attribute:
#OneToMany(mappedBy = "asset")
private Set<Dealing> dealings;
Second, using the criteria API for such static queries is overkill, and leads to code that is harder to read than necessary.I would simply use HQL which is much easier to read. Criteria should be used for dynamic queries, IMHO, but not for static ones:
select asset from Asset asset
inner join asset.dealings dealing
where dealing.customer.id = :customerId
and dealing.product.id = :productId
Whether you use HQL or Criteria, you can't use asset.dealings.customer, since asset.dealings is a collection. A collection doesn't have a customer attribute. To be able to reference properties from the Dealing entity, you need a join, as shown in the above HQL query. And it's the same for Criteria:
Criteria criteria = session.createCriteria(Asset.class, "asset");
criteria.createAlias("asset.dealings", "dealing"); // that's an inner join
criteria.add(Restrictions.eq("dealing.customer.id", customerId);
criteria.add(Restrictions.eq("dealing.product.id", productId);

Categories

Resources