I've got the folowing problem.
I write Regression-Tests against a databse. At the end i would clean up the created entities.
Everything works except the deletion of one entity.
The error is:
View or function 'OrganizationUserView' is not updatable because the modification affects multiple base tables.
The JPA-Annotation is the following:
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "OrganizationUserView", joinColumns = {
#JoinColumn(name = "OrganizationID")},
inverseJoinColumns = {
#JoinColumn(name = "MemberID")}
)
private Set<Member> members = new HashSet<>();
The Organization and the Member-Objects are already deleted before.
Has anyone an idea how can i delete the Object?
For now, i've used a Database-Trigger
CREATE TRIGGER IO_Trig_DEL
ON [dbo].[OrganizationUserView]
INSTEAD OF DELETE
AS BEGIN
SET NOCOUNT ON;
END
GO
Create a separate entity representing the join table, mapped to the view, instead of using #JoinTable.
Discussed here (forum is dead so using archive.org): https://web.archive.org/web/20170113182538/https://forum.hibernate.org/viewtopic.php?f=1&t=985505&sid=dc550e634938fa271b76ecf60119d189
Related
I am working on a project and I have felt like in #OneToMany Unidirectional association with #JoinColumn in JPA with springboot generates extra queries. For example if we have 2 entities
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JoinColumn(name = "post_id")
private List<PostComment> comments = new ArrayList<>();
//Constructors, getters and setters removed for brevity
}
#Entity(name = "PostComment")
#Table(name = "post_comment")
public class PostComment {
#Id
#GeneratedValue
private Long id;
private String review;
//Constructors, getters and setters removed for brevity
}
when we run the following code
Post post = new Post("First post");
post.addComment(
new PostComment("My first review")
);
post.addComment(
new PostComment("My second review")
);
post.addComment(
new PostComment("My third review")
);
entityManager.persist(post);
it generates following queries
insert into post (title, id)
values ('First post', 1)
insert into post_comment (review, id)
values ('My first review', 2)
insert into post_comment (review, id)
values ('My second review', 3)
insert into post_comment (review, id)
values ('My third review', 4)
update post_comment set post_id = 1 where id = 2
update post_comment set post_id = 1 where id = 3
update post_comment set post_id = 1 where id = 4
Now my question is that why does JPA updates post_comment records after inserting post_comment records ? why didn't JPA insert the post_comment records with the post_id while it was inserting records in post_comment table so that it don't have to update records again ?
You are depending on the CascadeType.ALL to insert the PostComments for you. Why is that? I consider that a JPA anti-pattern. JPA specifies that the owner of the relationship will persist relations. See If the relationship is bidirectional, the mappedBy element must be used to specify the relationship field or property of the entity that is the owner of the relationship. You have not specified an owner of the relationship.
What in the JPA specification can you point to that dictates that using the CascadeType should be implemented in any specific fashion?
What you show kind of makes sense to me. The Post is saved, the PostComments are saved b/c of the CascadeType, and then the Post id updated into the PostComments. You didn't set the Post id into the PostComments yourself so you don't have any say about how the implementation does it. I have seen CascaseType do some interesting things, but it's still an anti-pattern as far as I'm concerned. If you don't understand it, don't use it!
Besides that, you have also added a static = new ArrayList<>() on comments. Another anti-pattern. The majority of those new ArrayLists will be tossed onto the garbage heap in very short order. Even though Java manages memory for you, you should have some idea of how you are using it.
Short answer, the PostComments should be saved by you specifically, but only when you already have saved the Post and it is ready to be set into the PostComment. Of course, you should have the Post and the ManyToOne in the PostComment so you can do that.
I don't know if you've investigated the Query behaviors of the related Entities, but I think you'll find more surprises there as well. Since I have already done that, I make these comments.
References:
What is the "owning side" in an ORM mapping?
37.1 Entities
In our "Process" table there is a "Type" column. This column's valueset is defined in an enum in our code. However there are obsolete rows in this table. Meaning that there are rows where "type" is a value that is not present in the code's enum. The problem is whenever we acces ANY (not the obsolete ones) of the rows in this table we get an error that there is an unkown value for that column. Is there a way to disable this feature in hibernate as we do not want to delete these rows.
#EqualsAndHashCode(callSuper = true, of = {})
#Table(name = ProcessEntity.TABLE_NAME)
public class ProcessEntity extends BaseEntity implements ValidityHolder {
public static final String TABLE_NAME = "PROCESS";
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private Set<ConsentAnswerEntity> consentAnswers;
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private List<ProcessConsentEntity> processConsents;
#OneToMany(mappedBy = "consent", cascade = CascadeType.ALL)
private Set<ProcessTypeConsentEntity> processTypeConsents;
#Enumerated(EnumType.STRING)
#Column(name = "TYPE_ID")
private Type TpyeId;
If these "obsolete" records no longer fit into your Hibernate data model, then I recommend just moving them to some archive table. After all, you can't really select them now anyway using Hibernate, so at least at the application level, they serve no purpose.
For a more general way to logically delete a record without physically removing it, look into soft deletion. Using soft deletion, you would add a single boolean column to the table which, if marked, would indicate that the record is logically no longer there.
I'm trying to query for a list of entities (MyOrders) that have mappings to a few simple sub-entities: each MyOrder is associated with exactly one Store, zero or more Transactions, and at most one Tender. The generated SELECT appears correct - it retrieves all the columns from all four joined tables - but afterwards, two more SELECTs are executed for each MyOrder, one for Transactions and one for Tender.
I'm using QueryDSL 4.1.3, Spring Data 1.12, JPA 2.1, and Hibernate 5.2.
In QueryDSL, my query is:
... = new JPAQuery<MyOrder>(entityManager)
.from(qMyOrder)
.where(predicates)
.join(qMyOrder.store).fetchJoin()
.leftJoin(qMyOrder.transactions).fetchJoin()
.leftJoin(qMyOrder.tender).fetchJoin()
.orderBy(qMyOrder.orderId.asc())
.transform(GroupBy
.groupBy(qMyOrder.orderId)
.list(qMyOrder));
which is executed as:
SELECT myorder0_.ord_id AS col_0_0_,
myorder0_.ord_id AS col_1_0_,
store1_.sto_id AS sto_id1_56_1_, -- store's PK
transactions3_.trn_no AS trn_no1_61_2_, -- transaction's PK
tender4_.tender_id AS pos_trn_1_48_3_, -- tender's PK
myorder0_.ord_id AS ord_id1_39_0_,
myorder0_.app_name AS app_name3_39_0_, -- {app_name, ord_num} is unique
myorder0_.ord_num AS ord_num8_39_0_,
myorder0_.sto_id AS sto_id17_39_0_,
store1_.division_num AS div_nu2_56_1_,
store1_.store_num AS store_nu29_56_1_,
transactions3_.trn_cd AS trn_cd18_61_2_,
tx2myOrder2_.app_name AS app_name3_7_0__, -- join table
tx2myOrder2_.ord_no AS ord_no6_7_0__,
tx2myOrder2_.trn_no AS trn_no1_7_0__,
tender4_.app_name AS app_name2_48_3_,
tender4_.ord_num AS ord_num5_48_3_,
tender4_.tender_cd AS tender_cd_7_48_3_,
FROM data.MY_ORDER myorder0_
INNER JOIN data.STORE store1_ ON myorder0_.sto_id=store1_.sto_id
LEFT OUTER JOIN data.TX_to_MY_ORDER tx2myOrder2_
ON myorder0_.app_name=tx2myOrder2_.app_name
AND myorder0_.ord_num=tx2myOrder2_.ord_no
LEFT OUTER JOIN data.TRANSACTION transactions3_ ON tx2myOrder2_.trn_no=transactions3_.trn_no
LEFT OUTER JOIN data.TENDER tender4_
ON myorder0_.app_name=tender4_.app_name
AND myorder0_.ord_num=tender4_.ord_num
ORDER BY myorder0_.ord_id ASC
which is pretty much what I'd expect. (I cut out most of the data columns for brevity, but everything I need is SELECTed.)
When querying an in-memory H2 database (set up with Spring's #DataJpaTest annotation), after this query executes, a second query is made against the Tender table, but not Transaction. When querying a MS SQL database, the initial query is identical, but additional queries happen against both Tender and Transaction. Neither makes additional calls to load Store.
All the sources I've found suggest that the .fetchJoin() should be sufficient (such as Opinionated JPA with Query DSL; scroll up a few lines from the anchor) and indeed if I remove them, the initial query only selects columns from MY_ORDER. So it appears that .fetchJoin() does force generation of a query that fetches all the side tables in one go, but for some reason that extra information isn't being used. What's really weird is that I do see the Transaction data being attached in my H2 quasi-unit test without a second query (if and only if I use .fetchJoin() ) but not when using MS SQL.
I've tried annotating the entity mappings with #Fetch(FetchMode.JOIN), but the secondary queries still fire. I suspect there might be a solution involving extending CrudRepository<>, but I've had no success getting even the initial query correct there.
My primary entity mapping, using Lombok's #Data annotations, other fields trimmed out for brevity. (Store, Transaction, and Tender all have an #Id a handful of simple numeric and string field-column mappings, no #Formulas or #OneToOnes or anything else.)
#Data
#NoArgsConstructor
#Entity
#Immutable
#Table(name = "MY_ORDER", schema = "Data")
public class MyOrder implements Serializable {
#Id
#Column(name = "ORD_ID")
private Integer orderId;
#NonNull
#Column(name = "APP_NAME")
private String appName;
#NonNull
#Column(name = "ORD_NUM")
private String orderNumber;
#ManyToOne
#JoinColumn(name = "STO_ID")
private Store store;
#OneToOne
#JoinColumns({
#JoinColumn(name = "APP_NAME", referencedColumnName = "APP_NAME", insertable = false, updatable = false),
#JoinColumn(name = "ORD_NUM", referencedColumnName = "ORD_NUM", insertable = false, updatable = false)})
#org.hibernate.annotations.ForeignKey(name = "none")
private Tender tender;
#OneToMany
#JoinTable(
name = "TX_to_MY_ORDER", schema = "Data",
joinColumns = { // note X_to_MY_ORDER.ORD_NO vs. ORD_NUM
#JoinColumn(name = "APP_NAM", referencedColumnName = "APP_NAM", insertable = false, updatable = false),
#JoinColumn(name = "ORD_NO", referencedColumnName = "ORD_NUM", insertable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "TRN_NO", insertable = false, updatable = false)})
#org.hibernate.annotations.ForeignKey(name = "none")
private Set<Transaction> transactions;
/**
* Because APP_NAM and ORD_NUM are not foreign keys to TX_TO_MY_ORDER (and they shouldn't be),
* Hibernate 5.x saves this toString() as the 'owner' key of the transactions collection such that
* it then appears in the transactions collection's own .toString(). Lombok's default generated
* toString() includes this.getTransactions().toString(), which causes an infinite recursive loop.
* #return a string that is unique per order
*/
#Override
public String toString() {
// use appName + orderNumber since, as they are the columns used in the join, they must (?) have
// already been set when attaching the transactions - primary key sometimes isn't set yet.
return this.appName + "\00" + this.orderNumber;
}
}
My question is: why am I getting redundant SELECTs, and how can I not do that?
I'm a little too late on the answer, but today the same problem happened to me. This response might not help you, but at least it would save someone the headache we went through.
The problem is on the relations between the entities, not in the query. I tried with QueryDSL, JPQL, and even native SQL but the problem was always the same.
The solution was to trick JPA into believing that the relations were there via annotating the child classes with #Id on those joined fields.
Basically you'll need to set Tender's id like this and use it from MyOrder like if it was a normal relationship.
public class Tender {
#EmbeddedId
private TenderId id;
}
#Embeddable
public class TenderId {
#Column(name = "APP_NAME")
private String appName;
#Column(name = "ORD_NUM")
private String orderNumber;
}
The same would go for the Transaction entity.
I have a few entities with lazy one to many relationships (logic omitted for brevity):
#Entity
class A{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "a_pk", nullable = false)
List<B> blist = new ArrayList<>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "a_pk", nullable = false)
List<C> clist = new ArrayList<>();
#Column(name = "natural_identifier", nullable = false)
private String id;
}
#Entity
class B{
}
#Entity
class C{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "c_pk", nullable = false)
List<D> dlist = new ArrayList<>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "c_pk", nullable = false)
List<E> elist = new ArrayList<>();
}
#Entity
class D{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "d_pk", nullable = false)
List<F> flist = new ArrayList<>();
}
#Entity
class E{
}
#Entity
class F{
}
In some (very rare) case I want to load an instance of A and all of its associations eagerly. The reason for that is that I want to make some modifications on that A instance and it's children as a whole, and then either save or discard them, depending on user input.
If I were to load things as they are needed, I'd have to reattach entities to a new session on user request, but they are being modified, and modifications should not be persisted yet.
So I write something like that:
Session session = sessionFactory.openSession();
s.beginTransaction();
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", SELECT)
.setFetchMode("clist", SELECT)
.createAlias("clist", "c")
.setFetchMode("c.dlist", SELECT)
.setFetchMode("c.elist", SELECT)
.createAlias("c.dlist", "d")
.setFetchMode("d.flist", SELECT);
A a = (A) c.uniqueResult();
session.close(); // line 150
a.getBlist().size(); // line 152 - debug
a.getClist().size(); // line 153 - debug
When I try to access stuff I get an exception on line 152:
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
A.blist, could not initialize proxy - no Session
If I change fetch strategy to JOIN everywhere in the criteria, I get the same exception, but on line 153 (in other words, the first association gets loaded, but not the others).
EDIT: Alexey Malev suggested that fetch mode is set for an alias; it does seem to be true. With a following criteria:
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", JOIN)
.setFetchMode("clist", JOIN);
I'm getting a different Exception: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags on line 149. So, join-fetching is not an option.
The question is, how do I load the whole thing?
Hibernate version 4.2.12.Final
I found a solution to my original problem by using a slightly different approach.
Quoting Hibernate ORM documentation:
Sometimes a proxy or collection needs to be initialized before closing the Session. You can force initialization by calling cat.getSex() or cat.getKittens().size(), for example. However, this can be confusing to readers of the code and it is not convenient for generic code.
The static methods Hibernate.initialize() and Hibernate.isInitialized(), provide the application with a convenient way of working with lazily initialized collections or proxies. Hibernate.initialize(cat) will force the initialization of a proxy, cat, as long as its Session is still open. Hibernate.initialize( cat.getKittens() ) has a similar effect for the collection of kittens.
Simply getting lazy collection (a.getBlist()) does not make it load - I initially made that mistake. If I try to get some data from that collection (get an item, get collection size) it will load. Calling Hibernate.initialize(..) on that collection will do the same.
So, iterating over entity associations, and their respective associations, etc, and explicitly initializing them (eg with Hibernate.initialize()) within session will load everything to be available outside the session once it's closed.
Criteria fetch modes are not used at all with that approach (why won't they work as documented is another question).
It is an obvious case of N+1 problem, but something I can live with.
I think you're using wrong fetch mode. Most likely you need JOIN.
Try this instead:
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", JOIN)
.setFetchMode("clist", JOIN)
.createAlias("clist", "c")
.setFetchMode("c", JOIN)
...//the same for others
Note - I have added fetch mode for alias too. The behavior when you're not able to load any list which has an alias leads to that guess..
For the record I had a similar problem due to the key value setFecthMode(key,...)
I was using table names instead of field names.
This is similar, but not identical, to:
Hibernate criteria query on different properties of different objects
I have a SpecChange record, which has a set of ResponsibleIndividuals; these are User records mapped by a hibernate join-table association. I want to create a Criteria query for a SpecChange which has the specified User in its ResponsibleIndividuals set, OR some other condition on the SpecChange.
I'm omitting most of the code for clarity, just showing the relevant annotations:
#Entity
class SpecChange {
#OneToMany
#JoinTable(name = "ri_id_spec_change_id", joinColumns = { #JoinColumn(name = "spec_change_id") }, inverseJoinColumns = #JoinColumn(name = "ri_id"))
#AccessType("field")
public SortedSet<User> getResponsibleIndividuals() { ... }
#Id
#Column(name = "unique_id")
#AccessType("field")
public String getId() { ... }
}
#Entity
class User { ... }
//user does not have a SpecChange association (the association is one-way)
What I want to do:
User currentUser = ...;
Criteria criteria = session.createCriteria(SpecChange.class);
...
criteria.add(Restrictions.disjunction()
.add(Restrictions.eq("responsibleIndividuals", currentUser))
.add(...)
);
criteria.list();
This generates wrong SQL:
select ... from MY_DB.dbo.spec_change this_ ... where ... (this_.unique_id=?)
...and fails:
java.sql.SQLException: Parameter #2 has not been set.
(I omitted another condition in the where clause, hence parameter #2 is the one shown. I am sure that 'currentUser' is not null.)
Note that the restriction references the wrong table: this_, which is SpecChange, not the User table
I tried a dozen different tricks to make it work correctly (including creating an alias, as mentioned in the previous post above). If there is a way to do it using the alias, I wasn't able to determine it.
The following DOES work (but doesn't accomplish what I need, since I can't use it in a disjunction):
criteria.createCriteria("responsibleIndividuals").add(Restrictions.idEq(currentUser.getId()));
[Edit: a workaround for what seems like a bug in Hibernate, using HQL]
select sc
from com.mycompany.SpecChange sc
left join fetch sc.responsibleIndividuals as scResponsibleIndividuals
where scResponsibleIndividuals = :p1 or sc.updUser = :p1
order by sc.updDate desc
This won't work without the alias "scResponsibleIndividuals"