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.
Related
I'm mapping a relationship that does not use the entity's primary key. Using "referencedColumnName" with a column different than the primary key causes hibernate to eagerly fetch the association, by issuing an extra select, even when it's tagged with FetchType.LAZY.
My goal is to make it behave like a regular mapping, meaning it wouldn't issue an extra query every time I need to query the main entity.
I have already tried using #LazyToOne(LazyToOneOption.NO_PROXY), which sorts out the problem, but it does not operate well with Jackson's (JSON parsing library) module "jackson-datatype-hibernate5", which skips hibernate lazy proxies when serializing the results.
Here is a scenario almost like the one I have that causes the problem:
Entities:
#Entity(name = "Book")
#Table(name = "book")
public class Book
implements Serializable {
#Id
#GeneratedValue
private Long id;
private String title;
private String author;
#NaturalId
private String isbn;
//Getters and setters omitted for brevity
}
#Entity(name = "Publication")
#Table(name = "publication")
public class Publication {
#Id
#GeneratedValue
private Long id;
private String publisher;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "isbn",
referencedColumnName = "isbn"
)
private Book book;
#Column(
name = "price_in_cents",
nullable = false
)
private Integer priceCents;
private String currency;
//Getters and setters omitted for brevity
}
Repository (Spring-Data, but you could try directly with the EntityManager):
#Repository
public interface PublicationRepository extends JpaReadRepository <Publication, Long>
{
#Query ("SELECT d FROM Publication d WHERE d.publisher = ?1 ")
Optional <Publication> findByPublisher (String isbn);
}
Thanks
The only way to achieve what you are looking for is by moving the annotatation #Id to the isbn property.
You can leave the #GeneratedValue on the autoincrement property.
Notes:
1 - Make sure that your equals/hc are following the OID(Object ID) on your domain case the "NaturalId" ISBN.
2 - It will be good to ensure if possible on DB level that your natural ID has unique contraint on it.
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/
Could you help me solve a small question?
It's about the project with Spring Data, JPA and Hibernate.
I have 2 entities and I need to join one to another. Yes, it sounds simple :)
First is Activity:
public class Activity {
...
#Column(name="STATUS_ID")
private String statusId;
...
}
Second is Vocab Value (Users can administer it as a simple list of values):
public class VocabValue {
...
// Type of vocab (in our case - 'ACT_STAT')
#Column(name="TYPE")
private String type;
// Name - code of Vocab value
#Column(name="NAME")
private String type;
// Value
#Column(name="VAL")
private String value;
...
}
In my case, I want to store in an Activity a name of Vocab Value and join it.
In the target state, my entity should look like:
public class Activity {
...
#Column(name="STATUS_ID")
private String statusId;
#... what?
private String status;
// Getter returns Vocab Value in accordance with the following join spec (pseudo-sql):
// select voc.value from VocabValue voc where voc.name = %statusId% and type = 'ACT_STAT'
public String getStatus() {
}
...
}
How should I configure my Activity entity to provide this?
Ok, Is it possible?
I'll be grateful for your advice!
Thank!
Update 13.01
I've tried this solution:
public class Activity {
...
#Column(name="STATUS_ID")
private String statusId;
#JoinColumn(name = "STATUS_ID", referencedColumnName = "NAME", insertable = false, updatable = false)
#Where(clause = "type = 'ACT_STAT'")
#ManyToOne(optional = true)
#NotFound(action = NotFoundAction.IGNORE)
private VocabValue status;
...
}
Moreover, I had to make VocabValue implementation of Serializable (VocabValue as Detached entity should be implemetation of Serializable interface in accordance with Hibernate spec).
public VocabValue implemets Serializable {...}
It works, but annotation #Where doesn't work: Join performed only with #JoinColumn specification, an additional criteria, defined in #Where clause doesn't apply.
Whether there are workarounds?
UPDATE 13.01 - 2
It was solved with following configuration:
#JoinFormula(value="SELECT v.ROW_ID FROM VOCAB v WHERE v.NAME=STATUS_ID AND v.TYPE='ACT_STAT'")
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
private VocabValue status;
Thanks!)
It was solved with following configuration:
#JoinFormula(value="SELECT v.ROW_ID FROM VOCAB v WHERE v.NAME=STATUS_ID AND v.TYPE='ACT_STAT'")
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
private VocabValue status;
Thank's )
When deleting an #Embeddable object, I run into some problems.
I have the following domain classes: SwitchVoipTrunkGroup and PrioritizedCodec. The latter contains several fields that are nullable.
class SwitchVoipTrunkGroup {
//...
#CollectionOfElements(fetch = FetchType.LAZY)
#JoinTable(
name = "SWITCH_VOIP_TKG_CODEC",
joinColumns = #JoinColumn(name = "FK_SWITCH_VOIP_TKG_ID")
)
#ForeignKey(name = "FK_CODEC_SWITCH_VOIP_TKG")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Embeddable
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
When I edit SwitchVoipTrunkGroup's prioritizedCodecs field (e.g. by deleting an entry) and save the entity, I see the following in my Hibernate logging:
13:54:31,919 INFO [STDOUT] Hibernate: delete from T_SWITCH_VOIP_TKG_CODEC where
fk_switch_voip_tkg_id=? and fax_mode=? and priority=?
From this question I understand why Hibernate uses all the fields in the where clause. However, this gives problems: in case some of these fields are empty, the query will look like so:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode = ''
and priority =''
This will however not delete any records, as what is really necessary is for Hibernate to check for NULL iso for an empty string. For example:
delete from T_SWITCH_VOIP_TKG_CODEC where fk_switch_voip_tkg_id=1 and fax_mode
IS NULL and priority IS NULL
(cf. here for more info on why checking for an empty string does not suffice)
Any ideas on how to tackle this? Many thx!
I suggest to normalize your database, so both your classes become entities, and then to setup One-to-Many relation between SwitchVoipTrunkGroup and PrioritizedCodec, then you may setup cascading rules so Hibernate automatically updates collection of elements of PrioritizedCodec type, when you persist instance of SwitchVoipTrungGroup.
#Entity
class SwitchVoipTrunkGroup {
//...
#OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, orphanRemoval = true)
#JoinColumn(name = "switchVoipTrunkGroup_id")
#ForeignKey(name = "FK_PrioritizedCodec_SwitchVoipTrunkGroup")
private Set<PrioritizedCodec> prioritizedCodecs = new HashSet<PrioritizedCodec>();
//...
}
#Entity
public class PrioritizedCodec {
#Column(name = "PRIORITY")
private String priority;
#Column(name = "FAX_MODE")
private String faxMode;
//... some more columns ...
}
#Serice("someService")
public class SomeService {
#Autowired
private SwitchVoipTrunkGroupDao trunkDao;
public SwitchVoipTrunkGroup doOperation("criteria") {
SwitchVoipTrunkGroup tg = trunkDao.find("criteroa");
tg.getPrioritizedCodecs().[remove(2)]; //remove should be implemened, that is just lame statement
tg.getPrioritizedCodecs().get(5).setFaxMod("ENABLED");
return trunkDao.save(tg); //hibernate will remove missing elements from PrioritizedCodec table, and will update necessary entities.
}
}
Alternatively, you may specify default values for priority and faxMode fields via attributes of #Column annotation and enforce nullable constraints
#Column(columnDefinition = "VARCHAR(20) default 'NONE'", nullable = false)
private String faxMode;
I would like to retrieve many 'Access' which have one 'Role' in common.
It's the named query:
SELECT access
FROM Access AS access
WHERE :role MEMBER OF access.listRole
The Access entity
public class Access implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String libelle;
#ManyToOne
private Module oneModule;
#ManyToMany
private List<Role> listRole;
/* Setter & Getter */
}
The Role entity
public class Role implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
private String description;
#Enumerated(EnumType.STRING)
private Flag oneFlag;
#Transient
private int lengthAccess;
#OneToMany(mappedBy="oneRole")
private List<UserAccount> listUserAccount;
#ManyToMany
private List<Access> listAccess;
/* Geter & Setter */
}
But I don't achieve to do the right EJB-QL !
Configuration:
EJB 3
MySQL (InnoDB)
jBoss
Plop
Thanks.
I cannot reproduce the problem. When running the JPQL query you provided, like this:
String qlString = "SELECT access " +
"FROM Access AS access " +
"WHERE :role MEMBER OF access.listRole";
Role role = new Role();
role.setId(1L);
List accesses = session.createQuery(qlString).setParameter("role", role).list();
Hibernate generates the following SQL query for me (I simplified a bit your entities by removing some attributes):
select
access0_.id as id127_,
access0_.libelle as libelle127_,
access0_.name as name127_
from
Access access0_
where
? in (
select
role2_.id
from
Access_ROLES listrole1_,
ROLES role2_
where
access0_.id=listrole1_.Access_id
and listrole1_.listRole_id=role2_.id
)
Everything seems correct (tested with Hibernate Core 3.3.0.SP1, Hibernate Annotations 3.4.0.GA, Hibernate EM 3.4.0.GA)
What version of Hibernate (Core, Annotations, EntityManager) are you using exactly? What error do you get exactly? Can you show how you invoke the query?
My link #ManyToMany between my two classes isn't write in the right way, during the project's building, 2 Tables has created in MySQL ("access_role" for my link #ManyToMany in the 'access' class, and role_access for my link #ManyToMany in the 'role' class)
So, to correct this, I modified like this
public class Access implements Serializable {
// ...
#ManyToMany(mappedBy="listAccess")
private List<Role> listRole;
// ...
}
public class Role implements Serializable {
// ...
#ManyToMany
#JoinTable(name = "access_role",
joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "access_id", referencedColumnName = "id"))
private List<Access> listAccess;
// ...
}