I am using jpa and creating sql independently whithout autogenerate sql,
have three tables village, population and village_population where village_population holds mapping.
My entity mapping is something like this
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "village_population", joinColumns = #JoinColumn(name = "village_id"),
uniqueConstraints = #UniqueConstraint(columnNames = {"village_id","population_id"}))
#Column(name = "population_id")
private final Set<String> populationIds = new HashSet<String>();
The merge when adding child is working fine but update is not working, when looking at sql logs it just shows update query and insert query for village table and insert query for collection table (village_population) but not running update query on village_population
Related
Our in-house framework built with Java 11, Spring Boot, Hibernate 5 and QueryDSL does a lot of auto-generation of queries. I try to keep everything efficient and load associations only when needed.
When loading full entities, the programmer can declare a NamedEntityGraph to be used. Now there is one case where a query like this is generated:
select user.groups
from User user
where user.id = ?1
Where the Entities in question look like this:
#Entity
#NamedEntityGraph(name = User.ENTITY_GRAPH,
attributeNodes = {
#NamedAttributeNode(User.Fields.permissions),
#NamedAttributeNode(value = User.Fields.groups, subgraph = "user-groups-subgraph")
},
subgraphs = #NamedSubgraph(
name = "user-groups-subgraph",
attributeNodes = {
#NamedAttributeNode(Group.Fields.permissions)
}
))
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = Permission.class)
#CollectionTable(name = "USERS_PERMISSIONS", joinColumns = #JoinColumn(name = "uid"))
private Set<Permission> permissions = EnumSet.of(Permission.ROLE_USER);
#ManyToMany(fetch = LAZY)
private Set<Group> groups = new HashSet<>();
}
#Entity
#NamedEntityGraph(name = Group.ENTITY_GRAPH,
attributeNodes = {
#NamedAttributeNode(value = Group.Fields.permissions)
})
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = Permission.class)
#CollectionTable(
name = "GROUPS_PERMISSIONS",
joinColumns = #JoinColumn(name = "gid")
)
#NonNull
private Set<Permission> permissions = EnumSet.noneOf(Permission.class);
}
When selecting either User or Group directly, the generated query simply applies the provided NamedEntityGraphs. But for the above query the exception is:
org.hibernate.QueryException:
query specified join fetching, but the owner of the fetched association was not present in the select list
[FromElement{explicit,collection join,fetch join,fetch non-lazy properties,classAlias=user,role=foo.bar.User.permissions,tableName={none},tableAlias=permission3_,origin=null,columns={,className=null}}]
I first tried the User graph, but since we are fetching Groups, I tried the Group graph. Same Exception.
Problem is, there is no easy way to add a FETCH JOIN to the generated query, since I don't know which properties of the association should be joined in anyway. I would have to load the Entitygraph, walk it and any subgraph and generated the right join clauses.
Some more details on Query generation:
// QueryDsl 4.3.x Expressions, where propType=Group.class, entityPath=User, assocProperty=groups
final Path<?> expression = Expressions.path(propType, entityPath, assocProperty);
// user.id = ?1
final BooleanExpression predicate = Expressions.predicate(Ops.EQ, idPath, Expressions.constant(rootId));
// QuerydslJpaPredicateExecutor#createQuery from Spring Data JPA
final JPQLQuery<P> query = createQuery(predicate).select(expression).from(path);
// Add Fetch Graph
((AbstractJPAQuery<?, ?>) query).setHint(GraphSemantic.FETCH.getJpaHintName(), entityManager.getEntityGraph(fetchGraph));
EDIT:
I can reproduce this with a simple JPQL Query. It's very strange, if I try to make a typed query, it will select a List of Sets of Group and untyped just a List of Group.
Maybe there is something conceptually wrong - I'm selecting a Collection and I'm trying to apply a fetch join on it. But JPQL doesn't allow a SELECT from a subquery, so I'm not sure what to change..
// em is EntityManager
List gs = em
.createQuery("SELECT u.groups FROM User u WHERE u.id = ?1")
.setParameter(1, user.getId())
.setHint(GraphSemantic.FETCH.getJpaHintName(), em.getEntityGraph(Group.ENTITY_GRAPH))
.getResultList();
Same Exception:
org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list
So the problem can be distilled down to a resolution problem of the Entit Graphs attributes:
select user.groups
from User user
where user.id = ?1
With the Entity Graph
EntityGraph<Group> eg = em.createEntityGraph(Group.class);
eg.addAttributeNodes(Group.Fields.permissions);
Gives an Exception that shows that Hibernate tries to fetch User.permissions instead of Group.permissions. This is the bug report.
And there is another bug regarding the use of #ElementCollection here.
I have an entity with #OneToMany relationship to a list of second entity.
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "PAYLD_ID")
#Fetch(FetchMode.SUBSELECT)
private List<KeyValueEntity> kvList;
So the subquery is done separately. My issue is how can I run my own query on subselect as I need to resolve some crossreferences?
I'd like to be able to, whenever the subquery gets executed for List to provide my own native or named query.
Can that be done in Hibernate (using version 4.2.21)?
Main entity is using native query:
Payload {
long id;
string cd
#OneToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
List<KeyValueEntity> kvList;
}
Named native query (short for brevity):
select pl.id, sct.cd from payload pl join SCT sct on sct.CSN = pl.TCSN
When the subquery gets executed to retrieve the kvList via the #OneToMany relationship, I see this query:
select kv.pl_id, kv.keycode, kv.value from KV kv where kv.pl_id=?
However, I'd like to change the subquery (the above) to :
select kv.pl_id, sct.CD, kv.value from KV kv join SCT sct on kv.keycode=sct.CDN where kv.pl_id=?
Is there a way to do that?
We did an upgrade to Hibernate 5, after which we began to experience performance issues.
We have several entities with associations like this:
#Entity
#Table(name = "EVENT")
public class Event {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "LOCATION", referencedColumnName = "ID")
private Location location;
}
#Entity
#Table(name = "LOCATION")
public class Location {
#Id
#Column(name = "ID")
private Long id;
}
We are using the Criteria API to fetch the data from the database.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Event> query = cb.createQuery(Event.class);
Root<Event> from = query.from(Event.class);
query.select(from).where(from.get("id").in(1, 2, 3));
TypedQuery<Event> tQuery = entityManager.createQuery(query);
tQuery.setMaxResults(1000);
tQuery.getResultList();
Previously (version 4, old Criteria API), Hibernate generated one select with a join statement that fetched all the data, just based on the FetchType.EAGER, but with Hibernate 5, it creates multiple additional queries for fetching the 'location' data - the N+1 problem.
Now, we have tried JPA Entity Graph, with mixed results. We were able to reduce the number of queries (no N+1 now), but on the other hand, the performance of the system is even slower.
My questions are:
what other ways are there to remove N+1 queries problem?
under what circumstances could Entity Graphs have negative performance impact?
(We use SQL Server, Tomcat, Hibernate 5.2.10, Java 8.)
To get the earlier behaviour of fetching location data along with join in a single query can be achieved by adding the fetch
from.fetch("location", javax.persistence.criteria.JoinType.LEFT);
So your code would look like:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Event> query = cb.createQuery(Event.class);
Root<Event> from = query.from(Event.class);
from.fetch("location", javax.persistence.criteria.JoinType.LEFT);
query.select(from).where(from.get("id").in(1, 2, 3));
TypedQuery<Event> tQuery = entityManager.createQuery(query);
tQuery.setMaxResults(1000);
tQuery.getResultList();
So although I personally hate soft deletes, im working in a project for which every table must only soft delete. Im not sure how to handle soft deletes on an association table, the field for which looks like this:
#ManyToMany(targetEntity = AdvertisementVendor.class, fetch = FetchType.EAGER)
#JoinTable(name = "advertisement_version_advertisement_vendor_association",
joinColumns = #JoinColumn(name = "advertisement_version_id"),
inverseJoinColumns = #JoinColumn(name = "advertisement_vendor_id"))
private Set<AdvertisementVendor> _advertisement_vendors = new HashSet<>();
I've seen how to do soft deletes, but I'm not sure how I would apply that to the association table.
UPDATE:
Taking Dragan Bozanovic's advice I updated my column to:
#ManyToMany(targetEntity = AdvertisementVendor.class, fetch = FetchType.EAGER)
#JoinTable(name = "advertisement_version_advertisement_vendor_association",
joinColumns = #JoinColumn(name = "advertisement_version_id"),
inverseJoinColumns = #JoinColumn(name = "advertisement_vendor_id"))
#WhereJoinTable(clause = "is_deleted = 0")
#SQLDelete(sql = "UPDATE advertisement_version_advertisement_vendor_association SET is_deleted = 1 WHERE advertisement_version_id = ? AND advertisement_vendor_id = ?", check = ResultCheckStyle.COUNT)
#SQLInsert(sql = "INSERT INTO advertisement_version_advertisement_vendor_association " +
"(advertisement_version_id, advertisement_vendor_id, is_deleted) VALUES(?, ?, 0) " +
"ON DUPLICATE KEY UPDATE is_deleted = 0")
private Set<AdvertisementVendor> _advertisement_vendors = new HashSet<>();
But this doesnt seem to be working. It seems to ignore #SQLDelete and just removes the mapping.
UPDATE 2:
Ignore the first update, it had to do with different code. The above example works as is.
You can use #WhereJoinTable for filtering conditions on join tables:
Where clause to add to the collection join table. The clause is
written in SQL. Just as with Where, a common use case is for
implementing soft-deletes.
I had a similar problem, and while your solution does work, I also had to add an #SQLDeleteAll annotation in addition to #SQLDelete.
The problem I had is it was still calling the default delete statement when I cleared all entries from the HashSet or deleted the parent object.
Apologies if my terminology is a little off, I'm still pretty new to Hibernate.
I have three tables , a main table MAIN_TB with foreign keys to table TCHARS and TSTATUSES, when i persist MAIN_TB, re-query and display the saved record, the joined columns(tchars from TCHARS and t_status from TSTATUSES) are null but data is saved. what could i be missing?
Table MAIN_TB
#JoinColumn(name = "T_STATUS", referencedColumnName = "T_STATUS")
#ManyToOne
private Tstatuses tStatus;
#JoinColumn(name = "T_CHAR", referencedColumnName = "T_CHAR")
#ManyToOne
private Tchars tChar;
Table TCHARS
#OneToMany(mappedBy = "tChar")
private Collection<MainTb> mainTbCollection;
Table TSTATUSES
#OneToMany(mappedBy = "tStatus")
private Collection<MainTb> mainTbCollection;
Code
public void saveMainTb(){
MainTb mainTb = new MainTb();
Tchars tchars = new Tchars();
Tstatuses tstatuses = new Tstatuses();
tchars.setTChar(new Short("1"));
mainTb.setTChar(tchars);
tstatuses.setTStatus(new Short("1"));
mainTb.setTStatus(tstatuses);
mainTb.setTName("test");
em.persist(mainTb);
}
Result
![Result][1]
Any help would be appreciated
since there is no cascading specified on either owning side or inverse side. I reckon you can try the following code to manually save all entity instances:
em.persist(tchars);
em.persist(tstatuses);
mainTb.setTChar(tchars);
mainTb.setTStatus(tstatuses);
em.persist(mainTb);
You can add the below code to your tStatus and tChar.
fetch=FetchType.EAGER
This will hit the performance, if you don't need to fetch the 2 above objects, always.
Instead, you can add the fetch clause in your query, to fetch the two child objects(tStatus and tChar), along with your mainTb object. This will save you the trouble and fetch all the child objects, you've specified in your fetch clause. Performance optimizer.