I am using soft delete in my project. I need to unload a lot of related entities. I am using #EntityGraph with multiple entities at the same time - entityA.EntityB.EntityC, this does not work on related entities.
Each entity has the #Where annotation. I've tried using #Where and #WhereJoinColumn entities on the field. It gave no results
#Entity
#Where(clause = "field is null")
public class EntityA {
#OneToOne(mappedBy = "entityA", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#PrimaryKeyJoinColumn
private EntityB entityB;
#Entity
#Where(clause = "field is null")
public class EntityB {
#MapsId
#OneToOne(fetch = FetchType.LAZY)
private EntityA entityA;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "id", nullable = false)
private EntityC entityC;
#Entity
#Where(clause = "field is null")
public class EntityC {
#OneToMany(mappedBy = "EntityB")
#Audited(targetAuditMode = NOT_AUDITED)
private Set<EntityB> set = new HashSet<>();
public interface EntityARepository extends JpaRepository<EntityA, Integer> {
#EntityGraph(attributePaths = {"entityB.entityC"})
Optional<EntityA> findById(Integer id);
I want Hibernate to return me a query like
SELECT * FROM EntityA
LEFT JOIN EntityB ON EntityA.id = EntityB.id
AND EntityB.field is null
LEFT JOIN EntityC ON EntityB.id = EntityC.id
AND EntityC.field is null
WHERE EntityA.id = ? AND EntityA.field is null
But it comes back to me
SELECT * FROM EntityA
LEFT JOIN EntityB ON EntityA.id = EntityB.id
LEFT JOIN EntityC ON EntityB.id = EntityC.id
AND EntityC.field is null
WHERE EntityA.id = ? AND EntityA.field is null
How do I get Hibernate to see all my #Where annotations when unloading many related entities. I wrote a query in hql, but it is too cumbersome.
Thanks
Related
I have 2 Entities:
public class Restaurant {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "restaurant")
private Set<Vote> votes;
}
and
public class Vote {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "restaurant_id", nullable = false)
private Restaurant restaurant;
}
if I try to get both of them like that
#Query("SELECT r FROM Restaurant r JOIN FETCH r.vote ")
I get Infinite Recursion with Jackson JSON. So I managed to find a way to handle that:
public class Restaurant {
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "restaurant")
private Set<Vote> votes;
}
public class Vote {
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "restaurant_id", nullable = false)
private Restaurant restaurant;
}
Now I can get restaurant with votes like that?
#Query("SELECT r FROM Restaurant r JOIN FETCH r.vote ")
But now I CAN'T GET Votes with restaurant
#Query("SELECT v FROM Vote v JOIN FETCH v.restaurant ")
because #JsonBackReference meant
private Restaurant restaurant;
wont be serialized. But i need both of this bidirectional relationship in my controllers. What should i do?
For serialization of entities with bidirectional relationship use #JsonIdentityInfo and remove the #JsonBackReference and #JsonManagedReference. The property of the #JsonIdentityInfo refer to your entity id property used to identify entity.
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Restaurant {
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Vote {
I want to use Hibernate annotations to represent a unidirectional one-to-many relationship using a join. I want an added condition on the join so it only happens when a column in the source table (the "one") is equal to a constant value. For example.
SELECT *
FROM buildings b
LEFT JOIN building_floors bf on bf.building_id = b.id AND b.type = 'OFFICE'
I want to represent the b.type = 'OFFICE' part of that query.
My question is quite similar to this one, except I have a condition on the source table. JPA/Hibernate Join On Constant Value
The Java entities look like this:
#Entity
#Table(name = "buildings")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
#Fetch(FetchMode.JOIN)
// buildings.type = 'OFFICE' ????
private Set<BuildingFloors> buildingFloors;
// getters/setters
}
#Entity
#Table(name = "building_floors")
public class BuildingFloor {
#Id
#Column(name = "building_id")
private int buildingId;
#Id
#Column(name = "floor_id")
private int floorId;
#Column(name = "description")
private String description;
// getters/setters
}
I've tried a few things where I have that placeholder comment:
#Where annotation
This doesn't work since that applies to the target entity.
#JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "'OFFICE'", referencedColumnName = "type")
})
This doesn't work because I get the following error (simplified for clarity): Syntax error in SQL statement "SELECT * FROM buildings b JOIN building_floors bf on bf.building_id = b.id AND bf.'OFFICE' = b.type"
A different #JoinColumns annotation
#JoinColumns({
#JoinColumn(name = "building_id", referencedColumnName = "id"),
#JoinColumn(name = "buildings.type", referencedColumnName = "'OFFICE'")
})
This doesn't work because when using a unidirectional OneToMany relationship, the referencedColumnName is from the source table. So I get the error: org.hibernate.MappingException: Unable to find column with logical name: 'OFFICE' in buildings
Thanks in advance!
Why not use inheritance ? (I use it with JPA, I never use hibernate directly)
#Entity
#Inheritance
#Table(name = "buildings")
#DiscriminatorColumn(name="type")
public class Building {
#Id
#Column(name = "id")
private int id;
#Column(name = "type")
private String type;
}
And :
#Entity
#DiscriminatorValue("OFFICE")
public class Office extends Building {
#OneToMany(mappedBy = "buildingId",
fetch = FetchType.EAGER,
cascade = {CascadeType.ALL},
orphanRemoval = true)
private Set<BuildingFloors> buildingFloors;
}
Create database View with the following select:
SELECT bf.* FROM building_floors bf JOIN buildings b on bf.building_id = b.id AND b.type = 'OFFICE'
Map it to a class OfficeBuildingFloors as an ordinary entity and then use #OneToMany for it in Building class.
Of course, you won't be able to modify such collection and to avoid any exception you can use #Immutable on OfficeBuildingFloors.
In my opinion you should create a specific query to achieve your goals, rather than put specific annotations with constant parameter. I'm not see you mention another frameworks besides Hibernate so I would give some example with Hibernate. In your Building class your unidirectional mappings look like this:
#OneToMany(fetch = FetchType.Lazy, cascade = {CascadeType.ALL}, orphanRemoval = true)
#JoinTable(name = "building_floors", joinColumns = #JoinColumn(name = "id"), inverseJoinColumns = #JoinColumn(name = "building_id")
private Set<BuildingFloor> buildingFloors;
Then you can fetch your data using TypedQuery like this.
TypedQuery<Customer> query = getEntityManager().createNamedQuery("select b from building b inner join fetch b.buildingFloors where b.type = 'OFFICE'", Building.class);
List<Building> result = query.getResultList();
My solutions is not Hibernate specific, actually you could perform this with simple JPA. Hope this can help you to achieve your goals.
As you want filter source table you could use #Loader annotation
#Entity
#Table(name = "buildings")
#Loader(namedQuery = "building")
#NamedNativeQuery(name="building",
query="SELECT * FROM buildings b"
+ " LEFT JOIN building_floors bf on bf.building_id = b.id"
+ " WHERE b.type = 'OFFICE' AND b.id = ?",
resultClass = Building.class)
class Building
Approach with view in DB would be better and more clearly, if it could be used inside DB also. Otherwise rename Building to something which explicitly represent filtering.
Another approaches to mention: #Filter, #FilterDef.
Hibernate version 4.3.10
I have parent/child relationship as described in the example. There are occasions when we expect only one row to be returned when querying for Provider. In this case, we limit the criteria by calling setMaxResults method to 1.
Dump of SQL revealed that hibernate makes outer join calls which ends up returning more than one row, but because of limit set on criteria, only first child row is read from database.
#Entity
#Table(name = "F_PROVIDER")
public class Provider {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "provider", orphanRemoval = true, fetch = FetchType.EAGER)
private final Set<CredentialFieldDefinition> credentialFieldDefinitionList;
}
#Entity
#Table(name = "F_CRED_FIELDS", uniqueConstraints = { #UniqueConstraint(columnNames = { "name", "provider_id" }) })
public class CredentialFieldDefinition {
#ManyToOne(targetEntity = Provider.class, optional = false)
#JoinColumn(name = "PROVIDER_ID", nullable = false, unique = false)
private Provider provider;
}
How do I convince to return me full child set when I am reading only row?
You can try using SUBSELECT fetch mode:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "provider", orphanRemoval = true, fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
private final Set<CredentialFieldDefinition> credentialFieldDefinitionList;
This way credentialFieldDefinitionList will be initialized in a separate query.
I have some problems with inheritance mapping. Here my database structure:
And associated entities:
AbstractEntity:
#MappedSuperclass
public abstract class AbstractEntity<ID extends Serializable> implements Serializable {
#Id #GeneratedValue(strategy = IDENTITY)
#Column(unique = true, updatable = false, nullable = false)
private ID id;
public ID getId() {
return id;
}
#SuppressWarnings("unused")
public void setId(ID id) {
this.id = id;
}
UserAcitvity entity:
#Entity #Table(name = "user_activity")
#Inheritance(strategy = JOINED)
#AttributeOverride(name = "id", column = #Column(name = "ua_id"))
public abstract class UserActivity extends AbstractEntity<Long> {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User user;
...
}
Comment entity:
#Entity #Table(name = "comment")
#PrimaryKeyJoinColumn(name = "cm_id")
public class Comment extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "cm_question_id")
private Question question;
...
}
Question entity:
#Entity #Table(name = "question")
#PrimaryKeyJoinColumn(name = "qs_id")
public class Question extends UserActivity {
...
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Answer> answers = new ArrayList<>();
#OneToMany(fetch = LAZY, cascade = ALL, mappedBy = "question")
private List<Comment> comments = new ArrayList<>();
...
}
Answer entity:
#Entity #Table(name = "answer")
#PrimaryKeyJoinColumn(name = "asw_id")
public class Answer extends UserActivity {
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "asw_question_id")
private Question question;
...
}
and User entity:
#Entity #Table(name = "user")
#AttributeOverride(name = "id", column = #Column(name = "user_id"))
public class User extends AbstractEntity<Long> {
...
#OneToMany(cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Answer> answers = new ArrayList<>();
#OneToMany(cascade = REMOVE)
private List<Comment> comments = new ArrayList<>();
...
}
Problem:
When I try to save or delete a User I get an exceptions:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [insert into user_question (user_user_id, questions_qs_id) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement
and:
org.hibernate.engine.jdbc.spi.SqlExceptionHelper : 147 = user lacks privilege or object not found: USER_ANSWER
Hibernate is trying to create a table: user_question and user_answer which me do not need.
What I should doing for fixes ?
I don't think you can achieve this by mapping the ManyToOne association to User generically in the UserActivity entity. That's probably too confusing for the JPA provider (Hibernate).
Instead, I think you need to map the association to User in each of the Question, Answer and Comment entities. Yes, I know that would be duplicated code, but it looks like the only way you will then be able to qualify the OneToMany mappings in User using the mappedBy reference.
For instance, your Question entity would have an association defined as:
#ManyToOne(cascade = { MERGE, PERSIST }, fetch = LAZY)
#JoinColumn(name = "ua_user_id")
private User questionUser;
Depending on how clever (or not) Hibernate is about the above association, you may need to specify the table="USER_ACTIVITY" in the JoinColumn annotation.
Then the User would have the OneToMany as:
#OneToMany(mappedBy="questionUser", cascade = REMOVE)
private List<Question> questions = new ArrayList<>();
Similarly for each of Answer and Comment.
Of course, I haven't tried this, so I could be wrong.
It's probably happening because when you set the #OneToMany mapping then the hibernate will create an auxiliary table that will store the id from the entities on the relationship.
In this case you should try the following:
#OneToMany(cascade = REMOVE)
#JoinColumn(name = "answer_id")
private List<Answer> answers = new ArrayList<>();
The #JoinColumn annotation will map the relationship without the creation of the auxiliary table, so it's pretty likely this solution will help you in this situation.
Try this mapping, this should work as you expect according to section 2.2.5.3.1.1 of the documentation:
#Entity
public class User {
#OneToMany(cascade = REMOVE)
#JoinColumn(name="user_fk") //we need to duplicate the physical information
private List<Question> questions = new ArrayList<>();
...
}
#Entity
public class Question {
#ManyToOne
#JoinColumn(name="user_fk", insertable=false, updatable=false)
private User user;
...
}
The reason why the auxiliary association is created, is that there is no way for Hibernate to know that the Many side of the relation (for example Question) has a foreign key back to User that corresponds to the exact same relation as User.questions.
The association Question.user could be a completely different association, for example User.questionCreator or User.previousSuccessfulAnswerer.
Just by looking at Question.user, there is no way for Hibernate to know that it's the same association as User.questions.
So without the mappedBy indicating that the relation is the same, or #JoinColumn to indicate that there is no join table (but only a join column), Hibernate will trigger the generic one-to-many association mapping solution that consists in creating an auxiliary mapping table.
The schema misses such association tables, which causes the error that can be solved with the mapping above.
If you want unidirectional one-to-many usage in your entity relationship.
Try with..JoinTable
#OneToMany(cascade = REMOVE)
#JoinTable(name = "user_question", joinColumns = {
#JoinColumn(name = "user_id")}, inverseJoinColumns = {
#JoinColumn(name = "qs_id")})
private List<Question> questions = new ArrayList<>();
I have 3 JPA entities such as:
#Entity
public class Link implements {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "network", referencedColumnName = "id")
private Network network;
//...
}
#Entity
public class Network implements LinkOwner {
#OneToMany(mappedBy = "network")
#Cascade(value = { CascadeType.ALL })
private Set<Link> links;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "project", referencedColumnName = "id", nullable = false)
private Project Network.project;
//...
}
#Entity
public class Project {
#OneToMany(mappedBy = "project", orphanRemoval = true)
#Cascade(value = { CascadeType.ALL })
#Fetch(FetchMode.SELECT)
private Set<Network> networks;
}
And I do a JPA query such as:
SELECT l FROM Link l left join fetch l.subnetwork sann
where sann.project.id = :projectId
and it generates a SQL query similar to:
select * from RMT6.link, SUBNETWORK where link.subnetwork = SUBNETWORK.id
and SUBNETWORK.project=?
How can I trigger a JPQL query that selects only the fields of the first entity and exclude those of the second one?
What do I need to change in my JPQL query?
Base on your entity relationship, you don't need to use JOIN query, I think.
SELECT * FROM LINK l WHERE l.network.project.id = :projectId