How to handle Enums as string in spring data jpa Specification - java

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

Related

Using Enum values in #Query JPQL and #CreationTimestamp hibernate

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

Fetch join hibernate conditionally or change the entity design to conditional fetch on children?

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.

efficiently loading collection of collections from database in hibernate

I have an web application with hibernate which manages data in multiple languages. Currently basically every request generates a shower of select statements on the languagetranslations. The models are roughly as following:
Data <1-1> Placeholder <1-many> languageTranslation <many-1> language
If I query for all/many Dataobjects, I see lots of single selects which select one languageTranslation for the placeholder. The SQL I optimally would want to generate:
SELECT * FROM data join placeholder join languagetranslation
WHERE data.placeholder_id = placeholder.id
AND languagetranslation.placeholder_id = placeholder.id
AND languagetranslation.language_id = ?
so that I get every data with placeholder with translation in one single call. The languagetranslations have an composite primary key of language_id and placeholder_id.
I have no HBM file, everything is managed with annotations. Modelcode (only relevant sections are shown):
#Entity
public class Data {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, optional = false)
#Fetch(FetchMode.JOIN)
private Placeholder content;
}
public class Placeholder {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "primaryKey.placeholder", cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
private Set<LanguageTranslation> languageTranslations = new HashSet<>();
}
public class LanguageTranslation {
#EmbeddedId
private LanguageTranslationPK primaryKey = new LanguageTranslationPK();
#Type(type = "org.hibernate.type.StringClobType")
private String text;
}
#Embeddable
public class LanguageTranslationPK {
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private TextPlaceholder textPlaceholder;
#ManyToOne(fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private Language language;
}
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
I experimented with FetchType and FetchMode but couldn't generate the behavior I want, it always single selects for single languageTranslations.
I also tried multiple ways to query, criteria based, HQL, and raw SQL. My current raw SQL query is the following:
String sql_query = "select data.*, lt.* from Data as data join languagetranslation as lt on data.content_id = lt.textplaceholder_id";
Query q = getSession().createSQLQuery(sql_query).addEntity("data", Data.class).addJoin("data.content_id", "data.title").addJoin("lt", "data.content.languageTranslations").setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
return q.list();
Am I doing something generally wrong here? How can I convince hibernate to get all entities in one single database call? Or is there some other methods to improve performance in my case (e.g. batch selecting)?
You may create proxy pojo which have your all entity variables with getter setter and constructor. then initialize this constructor in hibernate query so that you just get all needed data from database.
import com.proxy;
class userProxy{
private string name;
private string password;
private string address;
private int pincode;
private byte[] profilePic;
private int age;
public userProxy(string name,string password){
this.name = name;
this.password = password;
}
//Getter and setter of all variable...
}
Then use this constructor to Hibernate query like
select new com.proxy.userProxy(user.name,user.password) from usertable
Am I doing something generally wrong here?
No, you are not. That is how Hibernate works.
How can I convince hibernate to get all entities in one single database call
You have to use HQL or SQL query to do that. You do not need to have HBM file. It can be done through #NamedQueries / #NamedQuery annotation with list method.
There are many samples on Internet as example simple one:
http://www.mkyong.com/hibernate/hibernate-named-query-examples/

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="<>")

How to Map a table with another lookup table using JPA?

I have two tables:
1) Application(int appid, int statusid, String appname, String appcity with getter and Setter methods)
2) App_Status(int statusid,String statusDescription with setter and getter methods)
I want to map Application table with App_Status so that I don't have to query separately App_Status table in order to get the statusDescription. One thing I have to careful is that no matter what (Insert,update or delete) to the Application table the App_Status table should be unaffected means its a read only table which is maintained by the DBA internally and used only for lookup table.
I am using JPA annotations so please suggest how to handle this.
The following should work. Map an AppStatus entity on the App_Status table:
#Entity
public class AppStatus {
#Id
private Long id;
private String statusDescription;
// getters, setters, hashCode, equals...
}
And declare it with a one-to-one association in the Application entity:
#Entity
public class Application {
#Id
private Long id;
private String appName;
private String appCity;
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "statusid", nullable = false, insertable = false, updatable = false)
private AppStatus appStatus;
// getters, setters, hashCode, equals...
}
Pay a special attention to the following details:
I defined the fetch mode to EAGER (note that EAGER is the default if you don't define it) so that the AppStatus will be eagerly fetched when loading an Application.
I didn't define any cascading option so that no operation will be cascaded from Application to AppStatus.
to retrieve all Application, use a FETCH JOIN
FROM Application a JOIN FETCH a.appStatus

Categories

Resources