I'm having problems with a Hibernate criteria. I'm trying to make a Criteria where I look at the id of a member object of the class the query returns.
For example:
Criteria crit = session.createCriteria(Enquiry.class);
crit.add(Expression.eq("lecture.admin.id", userId));`
The result of this is an exception:
org.hibernate.QueryException: could not resolve property: lecture.admin.id of: xxx.yyy.Enquiry
The Enquiry class does contain the lecture variable, which in turn contains the admin variable. I have tried using lecture.id and that works fine.
Is there a limit to the number of levels you can go down the object hierarchy like this?
Thanks!
Code snippets:
public class Lecture extends TransferItem {
private User admin;
public User getAdmin() {
return admin;
}
}
The 'User' class extends the Person class, which in turn extends an Itemclass, which has the getId()method:
public Integer getId() {
if (id != null) {
return id;
}
return TransferBean.NOT_SET;
}
From the Hibernate mapping XML:
<class name="User" table="user">
<id column="user_id" name="id">
<generator class="increment"/>
</id>
...
<class name="Lecture" table="lecture">
<many-to-one class="User" column="user_fk" lazy="false" name="admin"/>`
This is the user table:
mysql> show columns from user;
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| user_id | int(11) | NO | PRI | | |
| firstname | varchar(50) | YES | | NULL | |
| lastname | varchar(50) | YES | | NULL | |
| signature | varchar(16) | YES | | NULL | |
| email_signature | varchar(256) | YES | | NULL | |
| password | varchar(32) | YES | | NULL | |
| phone | varchar(16) | YES | | NULL | |
| email | varchar(255) | YES | UNI | NULL | |
| lecturer_fk | int(11) | YES | MUL | NULL | |
| access | int(11) | YES | | NULL | |
| deleted | tinyint(1) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+-------+
11 rows in set (0.02 sec)`
You can't use nested paths directly in Criteria API (unlike HQL). Instead, you need to create nested criteria instances or define aliases on each "entity.property" pair starting with the first non-root entity:
Criteria criteria = session.createCriteria(Enquiry.class)
.createAlias("lecture", "l")
.createAlias("l.admin", "a")
.add( Restrictions.eqProperty("a.id", userId) );
Note that the very first property is not prefixed as it belongs to the root entity (Enquiry), the others are prefixed with previous level alias. Details are in the documentation.
Also note that id is a special property when it comes to associations; in your case user_fk is a column located in lecture table. It should, therefore, be possible (provided that the mappings you've posted are accurate) to rewrite the above criteria as:
Criteria criteria = session.createCriteria(Enquiry.class)
.createAlias("lecture", "l")
.add( Restrictions.eqProperty("l.admin.id", userId) );
thus eliminating extra join.
Criteria crit = session.createCriteria(Enquiry.class)
crit.createAlias("lecture.admin", "lectureAdmin");
crit.add(Expression.eq("lectureAdmin.id", userId));
You can indeed hit issues when you go down too deep in the object graph. I usually get around this by creating an alias as shown above.
Related
I've got a MySQL database schema with 3 tables as follows:
mysql> describe results;
+--------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| run_at | datetime | YES | | NULL | |
| trials | int(10) unsigned | YES | | NULL | |
+--------------+------------------+------+-----+---------+----------------+
mysql> describe result_details;
+------------------------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| results_id | int(10) | NO | | NULL | |
| summarys_id | int(10) | YES | | NULL | |
+------------------------+------------------+------+-----+---------+----------------+
mysql> describe summarys;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| mean | double | YES | | NULL | |
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+-------+------------------+------+-----+---------+----------------+
Where a Result object can have several ResultDetail members. However, semantically, it makes sense to have one of these details stand out among the rest as a 'overall' detail. Therefore, I have the following classes:
Result.java (some members and methods removed for brevity)
#Entity
#Table(name="results")
public class Result extends BaseEntity {
#Column(name="run_at")
#Temporal(TemporalType.TIMESTAMP)
private Date runAt;
#Column(name="trials")
private Integer trials;
#OneToOne(mappedBy="result", cascade = {CascadeType.ALL})
private ResultDetails overallStats;
#OneToMany(mappedBy="result", cascade = {CascadeType.ALL})
private List<ResultDetails> resultDetails = new ArrayList<ResultDetails>();
}
ResultDetail.java
#Entity
#Table(name="result_details")
public class ResultDetails extends BaseEntity {
#ManyToOne
#JoinColumn(name = "results_id", nullable=false)
#NotNull
private Result result;
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name="summarys_id", nullable=true)
private Summary summary;
}
When I create persistent entities from my main as follows:
public static void main (String [] args) {
Result result = new Result();
ResultDetails detail1 = new ResultDetails();
ResultDetails detail2 = new ResultDetails();
Summary s1 = new Summary();
Summary s2 = new Summary();
result.setRunAt(new Date());
result.setTrials(1000000);
detail1.setResult(result);
s1.setMean(3.0);
detail1.setSummary(s1);
result.setOverallStats(detail1);
detail2.setCybervarResult(result);
s2.setMean(11.0);
detail2.setSummary(s2);
result.addDetails(detail2);
Session session = HibernateUtil.getSessionFactory().openSession();
session.beginTransaction();
session.saveOrUpdate(result);
session.getTransaction().commit();
HibernateUtil.shutdown();
}
It adds the appropriate rows to the tables. However, when I retrieve the data as demonstrated by the following additional main file:
Session session = HibernateUtil.getSessionFactory().openSession();
Result result = session.get(CybervarResult.class, 6);
result.getOverallStats().getSummary();
result.getResultDetails().size();
HibernateUtil.shutdown();
System.out.println(result.getOverallStats().getSummary().getMean());
System.out.println(result.getResultDetails().get(0).getSummary().getMean());
Hibernate is able to correctly populate the 'overallStats' and 'resultDetails' objects. How is it able to differentiate the two rows in the result_details table? As far as I can tell, there is nothing to distinguish the two from each other. Does Hibernate implement hidden tables/rows to manage which member variables correspond to which rows? If I were to create this database from mysql queries instead of through the hibernate API, how would Hibernate know which row should be the 'overallStats' and which rows should belong to the 'resultDetails' collection?
For reference, the rows created look as follows:
mysql> select * from results;
+----+---------------------+---------+
| id | run_at | trials |
+----+---------------------+---------+
| 6 | 2017-11-13 09:27:52 | 1000000 |
+----+---------------------+---------+
mysql> select * from result_details;
+----+------------+-------------+
| id | results_id | summarys_id |
+----+------------+-------------+
| 10 | 6 | 14 |
| 11 | 6 | 15 |
+----+------------+-------------+
hibernate.cfg.xml
<property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/usersdb</property>
<property name="hibernate.connection.username">test</property>
<property name="hibernate.connection.password">test</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">validate</property>
I am using hibernate/hql to create my querys.
Now I got a problem I'm stuck on for quite some hours now, to understand the situation here is
my sorrounding:
I got 3 Tables I need to get Informations from, with the connection/assignment tables I got 5 overall.
The Informations I need are the Key, the Type and the SourceFile, but the special case here is
that the sql will be done while importing new data, so I want to first check if the data is
already existent, or partially existent.
My query needs to always give me the key, no matter what Type or SourceFile
if the Key itself is already in the Database, also I then only want the key and not the other
Informations. (Key matches but SourceFile and Type do not, so I want only the key back)
If the Key is existent with the exact same Type and SourceFile I want to get all Informations.
The Tables are:
(heads up: FK_K_ID is saved as a object with the name key,
FK_S_ID is saved as Source and FK_T_ID is saved as Type)
Key:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| K_ID | bigint(20) | NO | PRI | NULL | auto_increment |
| key | varchar(255) | NO | | NULL | |
| deleted | boolean | NO | | FALSE | |
+-------------+--------------+------+-----+---------+----------------+
KeyType:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| KT_ID | bigint(20) | NO | PRI | NULL | auto_increment |
| FK_K_ID | bigint(20) | NO | | NULL | |
| FK_T_ID | bigint(20) | NO | | NULL | |
| deleted | boolean | NO | | FALSE | |
+-------------+--------------+------+-----+---------+----------------+
Type:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| T_ID | bigint(20) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
| description | varchar(255) | NO | | NULL | |
| deleted | boolean | NO | | FALSE | |
+-------------+--------------+------+-----+---------+----------------+
KeySource:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| KS_ID | bigint(20) | NO | PRI | NULL | auto_increment |
| FK_K_ID | bigint(20) | NO | | NULL | |
| FK_S_ID | bigint(20) | NO | | NULL | |
| deleted | boolean | NO | | FALSE | |
+-------------+--------------+------+-----+---------+----------------+
Source:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| S_ID | bigint(20) | NO | PRI | NULL | auto_increment |
| sourceFile | varchar(255) | NO | | NULL | |
| deleted | boolean | NO | | FALSE | |
+-------------+--------------+------+-----+---------+----------------+
and here is what I tried so far:
from KeyType kt
right outer join kt.Key k
with k.name in (...)
where k.deleted = false
and ( kt.Type in (...) or kt is null )
The problem with this one is, it kind of does what I want. but I get all keys in the database and the KeyType only where it matches, else null. I don't want to get all keys I just want to get my keys I asked for.
from KeyType kt
right outer join kt.Key k
with k.name in (...)
where k.deleted = false
and (k.name in (...) and kt.Type in (...) or kt is null )
I don't even know what I tried here to be honest. I guess I tried to optimize the first query to only give the keys I asked for.
from KeyType kt
right outer join fetch kt.Key k
where k.deleted = false
and (k.name in (...) and kt.Type in (...) or kt is null )
I also tried to use fetch like this or in other variations but didn't work like I need it to.
from Key k
left outer join KeyType kt on kt.Key.id = k.id
left outer join KeySource ks on ks.Key.id = k.id
inner join Source s on ks.Source.id = s.id
where k.deleted = false
and k.name in (...)
and ( kt.appType in (...) or kt is null )
and ( s.SourceFile in (...) or s is null )
well as expected this doesn't work. I mean its quite simpel to see that it can't work but you try
a lot if you can't get a hang on it.
I tried many more combinations and variations of querys but with no luck. The first query is the closest I got.
I Hope somebody can help me.
PS: I can't change the mapping or the entitys now. I have to work with what I got.
UPDATE:
Alright so I am very close to solving the problem. My query now looks like this:
select k, case when kt.Type not in (...) then null
else 1 end
from KeyType kt
join kt.Key k
where k.name in (...)
Now the only thing I want to to is exchange the 1 with the actual object. But if I do this I get the error "org.hibernate.exception.GenericJDBCException: could not execute query" (running on an oracle db)
Can someone tell me how to solve it?
For my case and sorrounding the way I wanted to do it is not possible.
So I asked coworkers again and we came to the solution we have to do it in single querys.
Just saying this for anyone passing by this post with the same table configuration/problem.
I have created a many-to-one mapping in hibernate. The following is the setting
<many-to-one name="groups" class = "Groups" column="cgid" unique="true" not-null="true" cascade="all"/>
In mysql this creates a table with another column called cgid.
mysql> describe CONTACT
-> ;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| IDCONTACT | bigint(20) | NO | PRI | NULL | |
| FIRSTNAME | varchar(255) | YES | | NULL | |
| LASTNAME | varchar(255) | YES | | NULL | |
| EMAIL | varchar(255) | YES | | NULL | |
| addressId | bigint(20) | NO | UNI | NULL | |
| cgid | bigint(20) | NO | UNI | NULL | |
+-----------+--------------+------+-----+---------+-------+
Now, I need to query based on cgid name.
queryString = "from Contact where cgid = :id";
query = session.createQuery(queryString);
query.setParameter("id", gd.getGid());
contactl = query.list();
Hibernate is constantly complaining about it
could not resolve property: cgid of: domain.Contact [from domain.Contact c where c.cgid = :id]
Not sure, what could be done to resolve this problem. Any suggestions ?
Here queryString = "from Contact where cgid = :id" you are using HQL.cgid is database column name.You must write Contact class variable instead of cgid.And this variable must be mapped with cgid.If you use native sql query with hibernate you can use database column names but with hql can not use.
I am getting the following error from my Hibernate code:
com.mysql.jdbc.exceptions.MySQLSyntaxErrorException: Unknown column 'bulletin0_.bulletin_date' in 'field list'
There is no such bulletin_date column in my table, nor is there such a name in my model class. It's just called date. Here is the line where I'm getting the error.
Query query = session.createQuery("from Bulletin where approved = true");
Here is my model class (I'm leaving out the getters and setters):
public class Bulletin {
#Id
#Column(name="id")
#GeneratedValue
private int id;
#Column(name="date")
private String date;
#Column(name="name")
private String name;
#Column(name="subject")
private String subject;
#Column(name="note")
private String note;
#Column(name="approved")
private boolean approved;
}
Here is my table definition.
+----------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| date | varchar(10) | YES | | NULL | |
| name | varchar(30) | YES | | NULL | |
| subject | varchar(50) | YES | | NULL | |
| note | varchar(2500) | YES | | NULL | |
| approved | tinyint(1) | YES | | NULL | |
+----------+---------------+------+-----+---------+----------------+
I had the wrong column names in my Bulletin.hbm.xml file. When I corrected it, the problem was solved.
This is a real beginner Hibernate problem. I have a problem with the mappings for two tables. The first table is MARKET, and the second is MARKET_MENU_BRANCH which contains a list of rows for each MARKET row. When I save a new Market, I want it to insert both MARKET and MARKET_MENU_BRANCH rows, but in fact it seems to insert MARKET and then attempt an update on MARKET_MENU_BRANCH rows, which don't exist, causing an error. What I am doing wrong? My tables look like this:
mysql> describe MARKET;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(100) | YES | | NULL | |
+-------+--------------+------+-----+---------+-------+
mysql> describe MARKET_MENU_BRANCH;
+----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| marketId | int(11) | NO | PRI | 0 | |
| sequence | int(11) | NO | PRI | 0 | |
| name | varchar(100) | YES | | NULL | |
+----------+--------------+------+-----+---------+-------+
My domain objects look like:
public class Market implements Serializable {
private int id;
private String name;
private List<MarketMenuBranch> marketMenuBranches;
// accessors / mutators etc...
public class MarketMenuBranch implements Serializable {
private MarketMenuBranchId id;
private String name;
// accessors / mutators etc...
public class MarketMenuBranchId implements Serializable {
private int marketId;
private int sequence;
// accessors / mutators etc...
and my mappings look like:
<class name="Market" table="MARKET">
<id name="id"/>
<property name="name"/>
<list name="marketMenuBranches">
<key column="marketId"/>
<list-index column="sequence"/>
<one-to-many class="MarketMenuBranch"/>
</list>
</class>
and
<class name="MarketMenuBranch" table="MARKET_MENU_BRANCH">
<composite-id name="id" class="MarketMenuBranchId">
<key-property name="marketId"/>
<key-property name="sequence"/>
</composite-id>
<property name="name"/>
</class>
You have a parent/child relationship, you will find insights in this page of the doc.
Basically you need to save(...) the child or use the cascade attribute.