I am stuck with some problem about using JPQL with spring data
Here is my code snippets
#Entity
#Table
//...
public class Parent {
#Id
private String id;
#Enumerated(EnumType.STRING)
private Status status;
#CreationTimestamp
private OffsetDateTime createTs;
#UpdateTimestamp
private OffsetDateTime updateTs;
#OneToOne(mappedBy = "parent", cascade = CascadeType.MERGE, optional = false)
#PrimaryKeyJoinColumn
private Child child;
//... getters setters and constructors
}
#Entity
#Table
//...
public class Child {
#Id
// is primary key in child table and refers to parent.id
private String parentId;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#MapsId
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private Parent parent;
//... getters setters and constructors
}
#Repository
public class ParentRepository extends CRUDRepository<Parent, String> {
#Query(value =
"from Parent p " +
"left join Child c " +
"where p.status in ('status1','status2') " +
"and p.updateTs between :fromUpdateTS and :toUpdateTS")
List<Parent> customQuery(OffsetDateTime fromUpdateTS, OffsetDateTime toUpdateTS);
}
So my first problem is that in native sql this custom query works just fine but once i needed to switch to JPQL, i figured out that it seems like there is no way to use IN clause with Enum collections, that is not passed as a named parameter in the query and this query doesn't work. And same thing is about 'between' keyword for timestamps, i tried < and > instead of 'between', but didn't succeed. So question is - what is proper way to construct such a query using JPQL in #Query annotation for CrudRepository.
I would rather avoid adding additional named parameter like ':statuses' cause it doesn't make any sense at all.
Second question is when i use parentRepository.save(parent) everything works good and timestamps is being created correctly by hibernate. But! When i just remove 'optional = false' from One-to-one mapping in Parent entity class, it starts giving me an error from database about null value of not null field create_ts (createTs in java class). And i am extremely confused with the fact that mandatoriness of the related entity as a hint for hibernate affects timestamp generation of completely other field. I know that 'optional' tells hibernate either to load related entity or not for setting correct proxy and lazy loading doesn't work with optional one-to-one mappings.
Could someone explain this behaviour to me please? I would really appreciate it
Related
I'm currently struglling with a JPQL custom query supposed to be simple, at least at first glance.
Stack: Java 11, spring-boot 2.4.5, spring-boot-starter-data-jpa 2.4.8, hibernate-core 5.4.16, Postgre database.
CASE
I just need my JPQL query to retrieve 3 fields, coming from a parent entity and it's child/nested entity (mapped as a one to one unidirectional relationship), in a custom DTO, instead of an entity from the domain.
The domain is like this:
#Entity
#Table(name = "Item")
public class ItemEntity {
#Id
private Long id;
#NotNull
private String field1;
#ManyToOne
#JoinColumn(name = "nestedEntity_id", referencedColumnName = "id")
private NestedEntity nestedEntity;
//...
}
#Entity
#Table(name = "Nested")
public class NestedEntity {
#Id
private Long id;
#NotNull
private String field1;
#NotNull
private String field2;
//...
}
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class MyDTO {
#NotNull
private String myField;
private String concatedNestedFields;
private String otherNestedField;
//...
}
I thought it was easy, and did something like this:
#Query("SELECT new my.package.MyDto(itemEntity.field1, CONCAT(itemEntity.nestedEntity.field1, ' ', itemEntity.nestedEntity.field2), itemEntity.nestedEntity.field3) FROM ItemEntity itemEntity WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
PARTICULARITY
I don't know if it is relevant or not, but the nested entity fields are #NotNull annotated.
PROBLEM
The problem occurs when the nestedEntity is null: the query return nothing, despite the 'parent' ItemEntity exists.
If the nestedEntity is not null, the query works.
WHAT I TRIED
I tried to use the COALESCE() function, which returns the first non null value from the parameters we give, on every nestedEntity field, as following:
#Query("SELECT new my.package.MyDto(itemEntity.field1, COALESCE(CONCAT(itemEntity.nestedEntity.field1, ' ', itemEntity.nestedEntity.field2),'-'), COALESCE(itemEntity.nestedEntity.field3), '-') FROM ItemEntity itemEntity WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
But it doesn't work either.
UPDATE
I just tried something to eliminate some root causes.
If I run a JPA Named query provided by spring-data / JPA and returning the entity instead of my custom DTO, it works, even with a nested entity null. It does retrieve the entity with it's nested null entity.
The query is like:
ItemEntity findItemEntityByCountry(#Param("country") CountryEnum country);
I'm not sure what to conclude about that, but it may help those who understand JPA better than me (and that's a lot of people... XD).
I didn't find any online resources for that case and I'm a bit lost.
I would be very grateful if you guys could help me with this little surprisingly tricky query!
Thanks a lot for your time guys! Hope this can help others too :)
try the following left join
#Query("SELECT new my.package.MyDto(itemEntity.field1,
CONCAT(nestedEntity.field1, ' ', nestedEntity.field2),
nestedEntity.field3)
FROM ItemEntity itemEntity left join itemEntity.nestedEntity as nestedEntity
WHERE itemEntity.country = :country")
MyDTO findByCountry(#Param("country") CountryEnum country);
otherwise don't use concat and simply pass nestedEntity.field2 and do the concatenation java side
I know for a fact that with clause on fetch join are not allowed by hibernate
I am using spring data jpa and postgres.
Here is how my entity is designed
public class Organisation {
#Id
private Long id;
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.EXTRA)
private Set<Assignment> assignments = new HashSet<>();
#OneToMany(mappedBy = "organisation", cascade = CascadeType.ALL)
private List<Event> events;
}
public class Event {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisations_id", nullable = false)
private Organisation organisation;
#OneToMany(mappedBy = "event", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
}
public class EventValue {
#Id
private Long id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "event_id")
private Event Event;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "assignment_id")
private Assignment assignment;
}
public class Assignment {
#Id
private Long id;
#OneToMany(mappedBy = "assignment", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<EventValue> eventValues = new HashSet<>();
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
Kind of a three way mapping. What the above entity design says is:
one organisation can have many events
one events can have many event values
one organisation can have many assignments
one assignment can be mapped to only one organisation and whithin the event of this organisation it is supposed to have only one event value (but as per entity design above it can have set of values which is not directly mapped to assignment)
So, I tried to query something like this.
#Query("select assignment from Assignment left join fetch assignment.organisation org
left join fetch org.event event left join fetch event.eventValues eventValue
with eventValue.assignment.id=?1 where assignment.id=?1)
Assignment getByAssignmentId(Long id);
What am I trying to achive with the query ?
To get assignment with given (id) -> organisation -> list of activities with HashSet containing only ONE activity value mapped to assignment.
The query is obviously going to fail because of using with clause on fetch join. I somehow feel the entity has 3 way dependency so it might be wrong.
I do not want to generic jdbcTemplate solution or SqlResultMapping solution where we need to do some kind of projection and set values manually. Is there a ORM solution to solve this problem ?
The reason why a WITH or ON clause is disallowed for join fetches is pretty simple. Hibernate works on managed entities, which means, once the entities are managed by the current persistence context, changes done to these objects will be flushed back to the database at the end of the transaction.
Now, if you were allowed to use the WITH or ON clause in a join fetch, the querying itself could alter the managed state of a collection, which would lead to UPDATE/DELETE statements to flush the collection changes back. Since this is completely unexpected, but a necessary side effect, it is disallowed.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Assignment.class)
interface AssignmentDto {
Long getId();
OrganisationDto getOrganisation();
}
#EntityView(Organisation.class)
interface OrganisationDto {
Long getId();
List<EventDto> getEvents();
}
#EntityView(Event.class)
interface EventDto {
Long getId();
#Mapping("eventValues[assignment.id = VIEW_ROOT(id)]")
EventValueDto getEventValue();
}
#EntityView(EventValue.class)
interface EventValueDto {
Long getId();
// Other stuff
}
The JOIN condition is modeled in the mapping expression eventValues[assignment.id = VIEW_ROOT(id)] which translates to what you would expect.
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
AssignmentDto dto = entityViewManager.find(entityManager, AssignmentDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
It will only fetch the mappings that you tell it to fetch.
Trying to use specification for filter data at database level.
I have an entity with another entity as an instance wherein the instance variable class contains an Emun field.
This defaults to a string in the database for the enum field.
#Entity
public class Outer{
#OneToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn(name = "current_status")
private Status current;
#OneToOne(fetch = FetchType.EAGER,cascade = CascadeType.ALL)
#JoinColumn(name = "past_status")
private Status past;
...
...
#Entity
public class Status{
#Enumerated(EnumType.STRING)
#Column(name = "state")
private State state;
#Id
#GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid",strategy = "uuid2")
#Column(name = "id")
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "outer_id")
private Outer outer;
I have created static meta models for both the classes.
How do I create a Predicate to match State using a where in clause with the enum values supplied as Strings(not instances of enums) ?
You modeled the entity attribute as an Enum and Hibernate, therefore, expects an enum in all JPQL or Criteria queries.
So, you either have to map all Strings to State enums or use a native SQL query.
Hibernate doesn't parse native queries, and they are based on the table model instead of the entity model. That allows you to use the String representation of your State enum.
You can do something like this:
List<State> states = ... // get or initialize your State list here
Query q = em.createNativeQuery("SELECT * FROM Status s WHERE state IN (:states)", Status.class);
q.setParameter("states", states);
List<Status> s = (List<Status>) q.getResultList();
The second parameter of the createNativeQuery method tells Hibernate to map each record of the result set to a Status entity. These entities are managed, and you can use them to update or remove the mapped database records.
To use this mapping, you need to make sure that your query selects all columns mapped by the entity. I wrote a series of posts that get into more details on the different result mapping options:
Result Set Mapping: The Basics
Result Set Mapping: Complex Mappings
Result Set Mapping: Constructor Result Mappings
Result Set Mapping: Hibernate specific features
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
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="<>")