Projection on hibernate relationship annotation - java

I have a two tables which are represented as below:
tableA {
private String code;
private String desc;
//few other properties followed by getter and setter
}
tableB {
private String code;
private String desc;
private String tableACode;
//few other properties followed by getter and setter
}
Now, I wanted to define a #ManyToOne relationship in tableB for tableA such that I wanted to store only the tableA.desc (I wanted to limit the size of this object in memory when it is cached).
Is this possible to do so?
I tried something like below.. But the resulting query is not what I'm expecting.
tableB {
private String code;
private String desc;
private String tableACode;
#ManyToOne
#JoinColumnsOrFormulas(value = {#JoinColumnOrFormula(column=#JoinColumn(name = "tableACode", referencedColumnName = "code")),
#JoinColumnOrFormula(formula = #JoinFormula(value = "SELECT a.desc from tableA e WHERE a.code = tableACode", referencedColumnName = "tableACode"))})
private String tableADesc;
//few other properties followed by getter and setter
}

It's possible, only if you create the EntityA to contain only the:
id
desc
Then you can simply use a #ManyToOne in EntityB:
#ManyToOne
private EntityA entityA;
So, even if your EntityA database table has multiple columns, you only persist the desc.
For new EntityA entries, all non-persisted columns will be set to NULL, which may break some NOT NULL constraints, so be aware of it.

Related

Translate native sql to JPA code or JPQL and make it pageable and sortable

I have two Entities like Below:
#Entity
#Table(name="tb_sm_config")
class Config {
#Id
private Long id;
private String code;
private String name;
#Enumerated(EnumType.STRING)
private State state;
#JsonIgnore
#OneToMany(mappedBy = "config", fetch = FetchType.LAZY)
private List<ConfigItem> items;
}
#Entity
#Table(name="tb_sm_config_item")
class ConfigItem {
#Id
private Long id;
private String code;
private String name;
#Enumerated(EnumType.STRING)
private State state;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "config_id", referencedColumnName = "id", foreignKey = #ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Config config;
}
the State is an Enum, there are three states : VALID, INVALID, DELETED
Then I define a ConfigVO like below:
class ConfigVO {
private Long id;
private String code;
private String name;
private State state;
private Long validItemCount;
private Long invalidItemCount;
pirvate Long deletedItemCount;
}
I want to provide fuzzy query based on config.name, config.code and precise query based on config.state and return a list of ConfigVO with pageable and sortable.
I know native sql will like :
select a.id, a.code, a.name, a.state,
coalesce(b._valid, 0) validItemCount,
coalesce(b._invalid, 0) invalidItemCount,
coalesce(b._deleted, 0) deletedItemCount
from tb_sm_config a left join
(select config_id, sum(if(state = 'VALID', 1, 0)) _valid, sum(if(state = 'INVALID', 1, 0)) _invalid, sum(if(state = 'DELETED', 1, 0)) _deleted from tb_sm_config_item group by config_id) b
on a.id = b.config_id
where code like %:code% and name like %:name% and state = :state
But here are the problems I don't know how to deal with:
The frontend will not always pass the code, name, state to query, so these three parameters is nullable.
If I use the #Query(nativeQuery=true, value=xxxx) in the ConfigRepository interface method, I don't know how to deal with the null value and the paging and sorting.
Is that any possible to achieve this goal through JpaSpecificationExecutor interface, Example interface, or something else?
I want to query base on the code, name, state, sometimes they will be null and want to count the total number of individual configItem states and pageable and sortable.

Nested entities contains null after save

I have an entity with some nested entities like this
public class MyEntity {
#ManyToOne
#JoinColumn(name="FK_ENTITY2")
private Entity2 fkEntity2;
#ManyToOne
#JoinColumn(name="FK_ENTITY3")
private Entity3 fkEntity3;
}
with entity2 and entity3 like this:
public class Entity2/3{
#Id
private Long id;
#Column(name = "code")
private String code;
#Column(name = "desc")
private String desc;
//getter and setter etc
}
Both Entity2 and Entity3 have values stored in the database so when I'm doing an insert on MyEntity, I'm doing this:
#Transactional
#Service
public MyService {
//idFromController and otherIdFromController refers to existent records in the database
public MyDto save(Long idFromController, Long otherIdFromController){
Entity2 entity2 = new Entity2();
entity2.setId(idFromController);
Entity3 entity3 = new Entity3();
entity3.setId(otherIdFromController);
MyEntity newMyEntity = new MyEntity();
newMyEntity.setEntity2(entity2);
newMyEntity.setEntity3(entity3);
MyEntity result = myEntityRepository.saveAndFlush(newMyEntity);
return getDto(result);
}
}
it works fine, the data are stored correctly in the DB with the correct foreign keys BUT...
After insert I want to build a DTO which contains id, code and desc from the nested entities so something like this:
private MyDto getDto(MyEntity result){
MyDto dto = new MyDto();
dto.setId2(result.getEntity2().getId());
dto.setCode2(result.getEntity2().getCode());
dto.setDesc2(result.getEntity2().getDesc());
dto.setId3(result.getEntity3().getId());
dto.setCode3(result.getEntity3().getCode());
dto.setDesc3(result.getEntity3().getDesc());
}
Here is the problem, I only got the id fields and null on code and description.
When I call getDto() in a search it works and every field has the correct values, so it is something related to the insert transaction? how could I solve this?
When you create the DTO, the (existing) entities are not attached to the persistence context, so the corresponding data has not been loaded from the DB and cannot be copied to the DTO.
One option would be to load the entities e.g. via 'myRepository.findById...' and associate the returned (managed) entities.
you missed some part of the many-to-one relation. first in MyEntity class it was better to define a fetch type. in Entity2 and Entity3 you need to define #OneToMany as the other side of the relation with declaration of fetch type and cascade = CascadeType.ALL which simply tells to hibernate what it can do with relative entity when you are doing save, delete ,update. I reformat your code as following
public class Entity2/3{
#Id
private Long id;
#Column(name = "code")
private String code;
#Column(name = "desc")
private String desc;
#OneToMany(cascade = CascadeType.ALL,mappedBy ="fkEntity2",fetch=FetchType.LAZY)
private List<Entity2> entityTwoList;
// for Entity3
#OneToMany(cascade = CascadeType.ALL,mappedBy ="fkEntity3",fetch=FetchType.LAZY)
private List<Entity3> entityThreeList;
public class MyEntity {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="FK_ENTITY2")
private Entity2 fkEntity2;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="FK_ENTITY3")
private Entity3 fkEntity3;
}
}

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 #Where annotation on a ManyToOne relation

I have recently started to refactor my project because I had to add an extra column to some of my table. The extra column is an Enum (Pending, or Active).
Because of that change I would need now to refactor ALL my queries to only retrieves a row if the status is ACTIVE.
After some research I found that we can annotate an Entity with the #Where annotation. it works fine where I use it on a simple column but my table look like this:
#Where(clause = 'state='ACTIVE'")
#Entity
public class Place {
#Column(name="id_place")
private String placeId;
#Column(name="name")
private String palceName;
#OneToMany(mappedBy = "place")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'state='ACTIVE'")
#Entity
public class Tag {
#Column(name="id_tag")
private String tagId;
#Column(name="name")
private String tagName;
#OneToMany(mappedBy = "tag")
private Set<PlaceTag> placeTag;
...
...
}
#Where(clause = 'poi.state='ACTIVE' AND tag.state='ACTIVE")
#Entity
public class PlaceTag {
#Column(name="id")
private String id;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "place_id")
private Place place;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "tag_id")
private Tag tag;
...
...
}
Now my question would be how can make this statement ONLY return the places and tags that are ACTIVE ?
SELECT pt FROM PlaceTag pt;
Is this possible? Or will I have to write the query Explicitly ?
Thank you
As you already discovered, or simply use cases the #Where clause is just fine, but in your case, you want to filter PlaceTag by the place and tag too, so a joined is required in this situation.
So, you can keep the #Where clause for Place and Tag, while for PlaceTags you need to use a JPQL query:
select pt
from PlaceTag pt
join pt.tag t
join pt.place p
where
t.state='ACTIVE' and p.state='ACTIVE'
At least until #WhereJoinTable annotation is made to work for many-to-one associations too.

Hibernate criteria on embedded id member member value

I would like to find an entity using a critera with restriction on the value of an attribute of a second entity wich is a member of the embedded id of my first entity.
First entity :
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Second entity :
#Entity
public class Owner {
#Id
#GeneratedValue (strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
#OneToMany (mappedBy = "id.owner")
private List<Car> cars;
// getters and setters...
}
In this example, I would like to obtain the car with the color 'black', model 'batmobile' and the owner's firstname 'Bruce' (oops... spoiler ;) )
I tried to do something like that but it won't work :
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("id.owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();
Result :
Hibernate: select this_.model as model1_0_0_, this_.owner_id as owner_id3_0_0_, this_.color as color2_0_0_ from Car this_ where this_.color=? and this_.model=? and o1_.firstname=?
ERROR: Unknown column 'o1_.firstname' in 'where clause'
What is the right way to obtain what I want ?
update
I tried in hql :
String hql = "FROM Car as car where car.color = :color and car.id.model = :model and car.id.owner.firstname = :firstname";
Query query = em.createQuery(hql);
query.setParameter("color", "black");
query.setParameter("model", "batmobile");
query.setParameter("firstname", "Bruce");
List<Car> cars = query.getResultList();
It works but is there a way to do this with criteria ?
You forgot to add the #Column annotation on top of the firstname and lastname fields (and the color field in Car). In hibernate if a field is not annotated, it doesn't recognize it as a database field. This page should give you a good idea about how to set up your model objects.
NOTE: You can have the column annotation over the getters and be fine, but you didn't show the getters. Either place is fine.
Look at what HQL is spitting back out, specifically the statement (formated for easier reading):
select
this_.model as model1_0_0_,
this_.owner_id as owner_id3_0_0_,
this_.color as color2_0_0_
from Car this_
where
this_.color=?
and this_.model=?
and o1_.firstname=?
It looks like hibernate is translating the field "id.owner" to "o" as your alias told it to to, but for some reason it's not writing down that "id.owner=o" as intended. You may want to do some research into why it may be doing that.
As per https://hibernate.atlassian.net/browse/HHH-4591 there is a workaround.
You have to copy the needed relation-property of the #EmbeddedId (owner in this case) to the main entity (Car in this case) with insertable = false, updatable = false as follows
#Entity
public class Car {
#EmbeddedId
private Id id = new Id();
private String color;
#ManyToOne
#JoinColumn(name = "column_name", insertable = false, updatable = false)
private Owner owner;
#Embeddable
public static class Id implements Serializable {
private static final long serialVersionUID = -8141132005371636607L;
#ManyToOne
private Owner owner;
private String model;
// getters and setters...
// equals and hashcode methods
}
// getters and setters...
}
Then just create directly the alias instead of using the composite id property
List<Car> cars = session.createCriteria(Car.class)
.add(Restrictions.eq("color", "black"))
.add(Restrictions.eq("id.model", "batmobile"))
.createAlias("owner", "o")
.add(Restrictions.eq("o.firstname", "Bruce"))
.list();

Categories

Resources