Why is Hibernate querying twice? - java

While testing implementation of JPA into Spring I found out that my query is querying twice instead of once.
#Data
#Entity
#Table(name = "superfan_star")
public class Star implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(nullable = false)
private int id;
private String name;
private String nickname;
private String description;
private String thumbnail;
private String backgroundImage;
private Date created;
private Date updated;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "starId", referencedColumnName = "id", insertable = false, updatable = false)
private Set<Media> medias;
}
This is model class.
#Service
public class SuperfanStarService
{
#Autowired
private StarRepository starRepository;
#PersistenceContext
private EntityManager em;
#Transactional
public List<Star> getStars()
{
QStar qStar = QStar.star;
QMedia qMedia = QMedia.media;
List<Star> stars =
new JPAQuery(em)
.from(qStar)
.where(qStar.id.eq(19))
.list(qStar);
return stars;
}
}
This is my service class.
20160915 20:52:59.119 [http-nio-8080-exec-1] DEBUG j.sqlonly - org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:82)
9. select star0_.id as id1_2_, star0_.background_image as backgrou2_2_, star0_.created as created3_2_, star0_.description as
descript4_2_, star0_.name as name5_2_, star0_.nickname as
nickname6_2_, star0_.thumbnail as thumbnai7_2_, star0_.updated as
updated8_2_ from superfan_star star0_ inner join superfan_media
medias1_ on star0_.id=medias1_.star_id where star0_.id=19
20160915 20:52:59.173 [http-nio-8080-exec-1] DEBUG j.sqlonly - org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:82)
9. select medias0_.star_id as star_id11_2_0_, medias0_.id as id1_1_0_, medias0_.id as id1_1_1_, medias0_.created as created2_1_1_,
medias0_.description as descript3_1_1_, medias0_.end_time as
end_time4_1_1_, medias0_.is_approve as is_appro5_1_1_,
medias0_.is_approved_final as is_appro6_1_1_, medias0_.is_pushed as
is_pushe7_1_1_, medias0_.is_represent as is_repre8_1_1_,
medias0_.length as length9_1_1_, medias0_.released as release10_1_1_,
medias0_.star_id as star_id11_1_1_, medias0_.teleport_media_id as
telepor12_1_1_, medias0_.thumbnail as thumbna13_1_1_, medias0_.title
as title14_1_1_, medias0_.work_end as work_en15_1_1_,
medias0_.work_start as work_st16_1_1_, medias0_.youtube_id as
youtube17_1_1_, medias0_.youtube_title as youtube18_1_1_ from
superfan_media medias0_ where medias0_.star_id=19
As you can see, it's querying twice instead of once, probably because of inverse update? Is there any way to make my JPA model query only once?

This works as expected. The first query gets the Star entity with id = 19 from the database, and the second query gets the linked Media entities for that Star entity from the database. (Carefully look at the log of the SQL statements to understand what is being queried).
Note that you specified FetchType.EAGER on the medias field in class Star:
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "starId", referencedColumnName = "id", insertable = false, updatable = false)
private Set<Media> medias;
Eager fetching means that when you do a query for one or more Star objects, Hibernate immediately gets the linked Media objects - as opposed to lazy fetching, which means that the second query will not be done immediately, but only when necessary (when you access the medias member variable).

While there is an accepted answer I suspect there maybe something else at play here. I note you have a Lombok #Data which I believe overrides equals() and hashcode() based on all fields which is dangerous in a JPA entity as it can trigger lots of additional data being loaded when associated items are added to hash based collections.
Yeah I found out that Lombok is causing problems for lists as it's querying medias for each Star. I'm trying to see if there's a way to use Lombok without querying everything but there doesn't seem to be a way.
Firstly, I would suggest not implementing equals() and hashcode() based on all fields of your entity: that is the root cause of your problem and makes no sense anyway - base them on a unique business key if you have one available. Essentially two entities are equal if they have the same id but see here however:
The JPA hashCode() / equals() dilemma.
Additionally, hashcode() should be based on immutable fields - see here:
http://blog.mgm-tp.com/2012/03/hashset-java-puzzler/.
Lombok's #Data just aggregates other individual annotations. So you can remove it, use the individual #Getter #Setter and #ToString Lombok annotations and write your own sensible implementations of equals() and hashcode() when required:
https://projectlombok.org/features/Data.html

Related

StackOverflowError on HashMap.hash for Lombok Hibernate Entities

I'm trying to make spring boot application. i want to get set of goals for each user. But i receive the error:
java.lang.StackOverflowError: null
at java.base/java.util.HashMap.hash(HashMap.java:339) ~[na:na]
at java.base/java.util.HashMap.get(HashMap.java:552) ~[na:na e.t.c.
In Goal:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
#OnDelete(action = OnDeleteAction.CASCADE)
private User userss;
In User:
#OneToMany(mappedBy = "userss", fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<Goal> goalSet = new HashSet<>();
And method for receiving goals for each user:
#Override
public Set findByUserId(Long userId){
return (Set) getSession().createQuery("from Goal where userss.id =:userId").setParameter("userId", userId).stream().collect(Collectors.toSet());
}
And method for receiving all users with their goals:
#Override
#SuppressWarnings("unchecked")
public Set getGoals() {
return (Set) getSession().createQuery("from Goal").stream().collect(Collectors.toSet());
}
I use lombok for hash (and actually for other basic methods)
If you are using Lombok for your entity, you must limit the generator
#Entity
#Data
#EqualsAndHashCode(onlyExplicitlyIncluded = true) // important
public class User {
#Id
#EqualsAndHashCode.Include // important, only on the PK
UUID id;
// Other fields not included in the generator
}
This is because, by default, Lombok generates the methods Equals and HashCode for every fields of your entity, but Hibernate requires the equality to be only done on the #Id field.
You have to do this for all your Entities.
Why was it crashing?
Because Goal references User, and User references Goal; when the default generator implemented Equals and HashCode, it would call both instances back and forth infinitely, until the crash.
Even without such cyclic references, Hibernate wouldn't know how to handle the entities in its cache without a proper Equals and HashCode.
You may need a similar limitation for #ToString to prevent recursive infinite loops.

Long field as foreign key in JPA entities, with on-cascade Delete

I'm having issues with defining a foreign key field within an entity. One specific thing that I can't find an answer to, is how to define such field but as a Long type, and not as that target entity type, and also set it up as ON DELETE CASCADE.
E.g.
#Entity
#Table(name = "user")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
and
#Entity
#Table(name = "address")
public class AddressEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(
table = "user",
name = "user_id",
referencedColumnName = "id")
private Long userId;
}
This example works fine, but now one can't easily define this DELETE ON CASCADE for the userId field i.e. Address entity.
One specific thing that I can't find an answer to, is how to define
such field but as a Long type, and not as that target entity type, and
also set it up as ON DELETE CASCADE.
It stands to reason that you cannot find an answer, because JPA does not provide one. If you want JPA to manage relationships between entities, then you must define those relationships in the JPA way, with entities holding references to other entity objects and declaring appropriate relationship annotations.* And if you want cascading deletes in your persistence context then you definitely do want them to be managed / recognized by JPA, for any other kind of approach is likely to create problems involving the context falling out of sync with the underlying data store.
It's unclear what problem you are trying to solve by avoiding JPA-style relationship management, but I'm inclined to think that there must be a better way. For example, if you want to avoid requiring the persistence context to load the associated UserEntity whenever an AddressEntity is loaded, then you would define the relationship with a lazy fetch strategy:
#Entity
public class AddressEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY)
private UserEntity user;
}
#Entity
public class UserEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY, cascade = CascadeType.ALL,
mappedBy = user)
AddressType address;
}
(Do note, however, that FetchType.LAZY is a hint, not a constraint. The context might sometimes still load the user together with its address if that's convenient.)
If you want to get the associated user id from an address, then the best way to do so is to read it from the user:
// ...
public Long getUserId() {
return (user == null) ? null : user.getId();
}
That does require the UserEntity to define an accessible getId() method, but since you are using JPA field-based access, you do not need also to provide a setter, and you may give the method default access. Or you could just declare UserEntity.id such that it is directly accessible by AddressEntity.
On the other hand, if you want to provide for the user ID to be accessible without loading the user entity then instead of a method such as the above getUserId(), in addition to the relationship field you could define a persistent, read-only AddressEntity.userId field, mapped to the appropriate column. It must be read-only because the value of the id in the underlying data store will necessarily be managed via the entity relationship, so it cannot also be managed via this separate field. For example:
#Entity
public class AddressEntity {
// ...
#OneToOne(optional = true, fetch = FetchType.LAZY)
private UserEntity user;
#Column(name = "user_id", insertable = false, updatable = false, nullable = true)
public Long userId;
}
This is a brittle approach, and I do not recommend it. It will be prone to problems with the userId field falling out of sync with the user entity. That may be bearable for the usage you have in mind, but this sort of weirdness is fertile ground for future bugs.
*Side note: as far as I know or can determine, JPA does not define semantics for a #JoinColumn annotation on a non-relationship field such as in your original code. That doesn't mean that your particular persistence provider can't interpret it in a way that you characterize as "works fine", but at minimum you are on thin ice with that.

JPA/validation #ManyToOne relations should not create new rows

I have an JPA entity with contains a ManyToOne reference to another table, a simplified version of that entity is shown below:
#Entity
#Table(name = "ENTITIES")
public class Entity implements Serializable {
#Id #NotNull
private String id;
#JoinColumn(name = "REFERENCE", referencedColumnName = "ID")
#ManyToOne(optional = false)
private ReferencedEntity referencedEntity;
}
#Entity
#Table(name = "REFERENCES")
public class ReferencedEntity implements Serializable {
#Id #NotNull #Column(name = "ID")
private String id;
#Size(max = 50) #Column(name = "DSC")
private String description;
}
Finding entities works fine. Peristing entities also works fine, a bit too good in my particular setup, I need some extra validation.
Problem
My requirement is that the rows in table REFERENCES are static and should not be modified or new rows added.
Currently when I create a new Entity instance with a non-existing (yet) ReferencedEntity and persist that instance, a new row is added to REFERENCES.
Right now I've implemented this check in my own validate() method before calling the persist(), but I'd rather do it more elegantly.
Using an enum instead of a real entity is not an option, I want to add rows myself without a rebuild/redeployment several times in the future.
My question
What is the best way to implement a check like this?
Is there some BV annotation/constraint that helps me restrict this? Maybe a third party library?
It sounds like you need to first do a DB query to check if the value exists and then insert the record. This must be done in a transaction in order to ensure that the result of the query is still true at the time of insertion. I had a similar problem half a year back which might provide you with some leads on how to set up locking. Please see this SO question.
You should add this => insertable=false, updatable=false
And remove => optional=false , and maybe try nullable=true

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);

Hibernate and JPA: how to make a foreign key constraint on a String

I am using Hibernate and JPA. If I have two simple entities:
#Entity
#Table(name = "container")
public class Container {
#Id
#Column(name="guid")
private String guid;
}
#Entity
#Table(name="item")
public class Item {
#Id
#Column(name="guid")
private String guid;
#Column(name="container_guid")
private String containerGuid;
}
and I want to insure that inserting an Item fails if the referenced Container does not exist. I would prefer not to have a Container object populated inside the item object (ManyToOne), how would I do this if it is possible to do?
You can declare arbitrary constraint using columnDefinition attribute:
#Column(name="container_guid",
columnDefinition = "VARCHAR(255) REFERENCES container(guid)")
private String containerGuid;
Note, however, that Hibernate doesn't know anything about this constraint, so that, for example, it may not perform inserts in proper order with respect of it and so on.
Therefore it would be better to create a #ManyToOne relationship. If you are afraid of extra SQL query for Container needed to set this property, you can use Session.load()/EntityManager.getReference() to get a proxy without issuing actulal query.
Try using below relationship mapping
RelationShip Mapping
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#ManyToOne()
#ManyToMany()
<>
#JoinColumn(name="<>")

Categories

Resources