I have hibernate XML where I'd like to retrieve a row as an instance of the subclass based on the results of a discriminator formula. Trouble is, the field to examine is on a referenced table, not on the table itself. My object is a merchant-user account, which is a combination of user account and merchant. The user_account table has a role field that may include several values, of which a few indicate a level of admin permissions. I'd like to do something like this, but this itself (trying to reference userAccount.role in the case statement) does not work:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="com.rc.model.mexp.MerchantUserAccount" table="merchant_user_account">
<id name="merchantUserAcctId" type="int">
<column name="merchantuseracctid" scale="10" precision="0" not-null="true" unique="true" sql-type="int unsigned"/>
<generator class="com.rc.model.jdbc.sequence.MexpIdentifierGenerator">
<param name="sequence">seq_merchantuseraccountid</param>
<param name="idDataType">int</param>
</generator>
</id>
<discriminator formula="case when userAccount.role in (2, 3, 4, 5) then '1' else '0' end" type="boolean"/>
<many-to-one name="userAccount" class="com.rc.model.user.security.UserAccount" column="userid"
unique="true" not-null="true" cascade="all"/>
<many-to-one name="merchantAccount" class="com.rc.model.mexp.MerchantAccount" column="merchantacctid"
not-null="true" cascade="all"/>
<subclass name="com.rc.model.mexp.AdminMerchantUserAccount" discriminator-value="true"/>
</class>
</hibernate-mapping>
Is this even possible?
Related
I have schema below:
Customer
id -Primary key
customer_name
phone
.............
Service
id -Primary key
customer_id -- foreign key to Customer.id
.................
Customer.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="thesis.database.Customer" table="customer">
<meta attribute="class-description">
</meta>
<id name="customerId" type="int" column="customer_id">
<generator class="native" />
</id>
<property name="phone" column="phone" type="string" />
<bag name="services" table="use_service" inverse="false" fetch="join" lazy="false"
cascade="all">
<key column="customer_id" />
<one-to-many class="thesis.database.Service" />
</bag>
</class>
Service.hbm.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="thesis.database.Service" table="service">
<meta attribute="class-description">
This class contains the Service detail.
</meta>
<id name="id" type="int" column="id">
<generator class="native" />
</id>
<many-to-one name="customer" class="thesis.database.Customer"
fetch="select">
<column name="customer_id" not-null="true" />
</many-to-one>
....................
</class>
My function
public static Customer getCustomerByPhoneNumber(String phoneNumber) {
Session session = getSession();
Criteria criteria = session.createCriteria(Customer.class);
Criterion phone = Restrictions.eq("phone", phoneNumber);
criteria.add(phone);
Customer customer = (Customer) criteria.uniqueResult();
session.close();
return customer;
}
And after that, i call
Customer customer = getCustomerByPhoneNumber("123456789"); // customer with this phone is availuable in database
I get this customer normally, but i cell getServices() function to get list service, it always get the same list, although i try to add more records to service table.
For example:
Customer table
id customer_name phone ................
1 Mr A 123456789................
and service table
id customer_id ........................
1 1 ........................
2 1 ........................
3 1 ........................
First query. i got list size = 3;
after insert one more record like that 4 1 ............ to service table
Second query. i also got list size = 3;
Can everybody tell me why and suggest any solution? thank in advance!
My solution is using Transaction to commit after adding new record.
Maybe you are forgetting to execute the criteria. Here http://docs.jboss.org/hibernate/orm/3.6/javadocs/org/hibernate/Criteria.html they are always calling the list method.
I am trying to use Hibernate to access persisted data for our rights management, but I am very new to it and struggling to get the data I need.
I have Users table, with an ID and name, a Groups table with an ID and name, and a User/Groups mapping which just consists of the group ids and user ids that are linked.
What I want to do is get all the names of the members of a given group, so the standard SQL query I want to execute is this:
SELECT
u.NAME
FROM
USERS u
JOIN
GROUP_USERS gu
ON
u.ID = gu.USER_ID
JOIN
GROUPS g
ON
gu.GROUP_ID = g.ID
WHERE
g.NAME = 'test'
But despite hours of looking and playing I cannot seem to get anywhere.
I want to use Criteria as they seem clearer, so my code is as follows:
#Override
public final List<String> getGroupMembers(final String groupName) {
#SuppressWarnings("unchecked")
List<User> groupUsers = getHibernateTemplate().execute(new HibernateCallback<List<User>>() {
#Override
public List<User> doInHibernate(Session session) throws HibernateException, SQLException {
Criteria criteria = session.createCriteria(User.class)
.setFetchMode("GroupUsers", FetchMode.JOIN)
.setFetchMode("Group", FetchMode.JOIN)
.add(Restrictions.eq("name", groupName));
return criteria.list();
}
});
List<String> groupUsernames = new ArrayList<String>();
for (User groupUser : groupUsers) {
groupUsernames.add(groupUser.getName());
}
return groupUsernames;
}
But when I test it the result set is empty, and according to the logs the executed query is this:
select this_.ID as M1_4_0_, this_.NAME as M2_4_0_ from USERS this_ where this_.NAME=?
I let Hibernate create the tables using hibernate.hbm2ddl.auto, but then removed it so that the tables are definitely as hibernate expects, but the data is not being cleaned.
Any help with the Criteria would be greatly appreciated, so thanks in advance!
Edit: I am using xml mapping files:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.Group" table="GROUPS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
</class>
</hibernate-mapping>
and
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.GroupUsers" table="GROUP_USERS">
<composite-id>
<key-many-to-one name="groupId" class = "Group" column="GROUP_ID" />
<key-many-to-one name="userId" class = "User" column="USER_ID" />
</composite-id>
</class>
</hibernate-mapping>
and
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.company">
<class name="com.company.User" table="USERS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
</class>
</hibernate-mapping>
You don't need to map the user-group table, just define your relation between User and Group as a many-to-many relation, take a look at this and this about how to map many-to-many relations.
In your case, it will look like:
<hibernate-mapping package="com.company">
<class name="com.company.User" table="USERS">
<id name="id" column="ID" type="int">
<generator class="identity"/>
</id>
<property name="name" column="NAME" type="java.lang.String" unique="true" not-null="true"/>
<many-to-many name="userGroups"
target-entity="com.company.Group">
<join-table name="YOUR_USER_GROUP_TABLE">
<join-column name="USER_ID" />
<inverse-join-column name="GROUP_ID" />
</join-table>
</many-to-many>
</class>
And to filter your users using the name field from the Group entity for example:
Criteria criteria = session.createCriteria(User.class);
criteria.createAlias("userGroups", "usrGrp",CriteriaSpecification.LEFT_JOIN);
criteria.add( Restrictions.eqProperty("usrGrp.name", "test") )
The way you map your object to tables does not benefit from the usage of Hibernate as ORM. Consider a more object-oriented model, eg:
class Group {
private Set<User> users;
// ...
}
class User {
private Set<Group> groups;
//..
}
So, you've got a classical many-to-many association. Here you can find an example of such mapping with Hibernate.
I have two one-to-one relations here between a class called "MailAccount" and the classes "IncomingServer" and "OutgoingServer".
(It's a Java application running on Tomcat and Ubuntu server edition).
The mapping looks like this:
MailAccount.hbm.xml
<hibernate-mapping package="com.mail.account">
<class name="MailAccount" table="MAILACCOUNTS" dynamic-update="true">
<id name="id" column="MAIL_ACCOUNT_ID">
<generator class="native" />
</id>
<one-to-one name="incomingServer" cascade="all-delete-orphan">
</one-to-one>
<one-to-one name="outgoingServer" cascade="all-delete-orphan">
</one-to-one>
</class>
</hibernate-mapping>
IncomingMailServer.hbm.xml
<hibernate-mapping>
<class name="com.IncomingMailServer" table="MAILSERVER_INCOMING" abstract="true">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<discriminator column="SERVER_TYPE" type="string"/>
<many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />
<subclass name="com.ImapServer" extends="com.IncomingMailServer" discriminator-value="IMAP_SERVER" />
<subclass name="com.Pop3Server" extends="com.IncomingMailServer" discriminator-value="POP3_SERVER" />
</class>
</hibernate-mapping>
OutgoingMailServer.hbm.xml
<hibernate-mapping>
<class name="com.OutgoingMailServer" table="MAILSERVER_OUTGOING" abstract="true">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<discriminator column="SERVER_TYPE" type="string"/>
<many-to-one name="mailAccount" column="MAIL_ACCOUNT_ID" not-null="true" unique="true" />
<subclass name="com.SmtpServer" extends="com.OutgoingMailServer" discriminator-value="SMTP_SERVER" />
</class>
</hibernate-mapping>
The class hierarchy looks like this:
public class MailAccount{
IncomingMailServer incomingServer;
OutgoingMailServer outgoingServer;
}
public class MailServer{
HostAddress hostAddress;
Port port;
}
public class IncomingMailServer extends MailServer{
// ...
}
public class OutgoingMailServer extends MailServer{
// ...
}
public class ImapServer extends IncomingMailServer{
// ...
}
public class Pop3Server extends IncomingMailServer{
// ...
}
public class SmtpServer extends OutgoingMailServer{
// ...
}
Now, here comes the problem:
Although most of the time my application runs well, there seems to be one situation in which email servers get deleted, but the corresponding account doesn't and that's when this call is made:
session.delete(mailAccountInstance);
In a one-to-one relation in Hibernate, the primary keys between mail account and its servers must be equal, if not, the relation completely gets out of sync:
Example:
Imagine, the tables are filled with data like this:
Table "MailAccount" (Current auto_increment value: 2)
MAIL_ACCOUNT_ID NAME
0 Account1
1 Account2
Table "IncomingMailServer" (Current auto_increment value: 2)
MAIL_SERVER_ID MAIL_ACCOUNT_ID
0 0
1 1
Now, image the account with ID=1 gets deleted and new accounts get added. The following then SOMETIMES happens:
Table "MailAccount" (Current auto_increment value: 3)
MAIL_ACCOUNT_ID NAME
0 Account1
1 Account2
2 Account3
Table "IncomingMailServer" (Current auto_increment value: 2)
MAIL_SERVER_ID MAIL_ACCOUNT_ID
0 0
1 2
This completely messes up my database consistency.
How can I avoid this?
If you want a shared primary key, you can use the native id generator only once. You create the mail account first, which will generate its own id, but when you create the Incoming- or OutgoingMailServer, these need to take their id from the mailAccount property.
So you need the "foreign" generator:
<class name="OutgoingMailServer">
<id name="id" column="MAIL_SERVER_ID">
<generator class="foreign">
<param name="property">mailAccount</param>
</generator>
</id>
<one-to-one name="mailAccount" not-null="true" constrained="true"/>
<class>
You don't need a MAIL_ACCOUNT_ID column, since it will always be identical to the MAIL_SERVER_ID anyway.
Quite basic follow the reference about bidirectional one-to-one association on a primary key.
I got a pretty simple parent/child relationship here, which looks like this:
Email servers have n folders.
Folders can have n (sub-)folders.
Folders have a reference to their parent folder as well as to the email server they belong to.
My mapping files look like this:
MailServer.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 02.05.2011 12:32:52 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
<class name="test.MailServer" table="MAILSERVER">
<id name="id" type="long" access="field">
<column name="MAIL_SERVER_ID" />
<generator class="native" />
</id>
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="MAIL_SERVER_ID"></key>
<one-to-many class="test.Folder" />
</bag>
</class>
</hibernate-mapping>
Folder.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated 04.05.2011 15:02:31 by Hibernate Tools 3.3.0.GA -->
<hibernate-mapping>
<class name="test.Folder" table="FOLDERS">
<id name="id" type="long" access="field">
<column name="FOLDER_ID" />
<generator class="native" />
</id>
<many-to-one name="mailServer" column="MAIL_SERVER_ID" not-null="true" />
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all">
<key column="PARENT_FOLDER_ID" not-null="false"></key>
<one-to-many class="test.Folder" />
</bag>
<many-to-one name="parentFolder" column="PARENT_FOLDER_ID" />
</class>
</hibernate-mapping>
The problem I got, is the following.
Let's say, I have the following hierachy:
- MyMailServer
Folder1
- Folder2
Subfolder
Folder3
When I call hibernateSession.save(mailServerInstance); or hibernateSession.update(mailServerInstance);, Hibernate correctly stores everything to the database. The parent column id's are filled correctly. Same for all other references.
BUT when I load the data, Hibernate reloads the folder hierachy like this:
- MyMailServer
Folder1
Folder2
Subfolder
Folder3
I understand the reason: Subfolder has a reference to its MailServer and thus, Hibernate ads it there instead of the folder where it belongs to.
But, how do I solve this problem?
Thanks in advance!
As you already understood what Hibernate does here is logical since you're fetching all folders for given mailServer. I don't think there is a way to achieve what you want in 1 single query (not a hibernate restriction, also with plain old SQL this is not possible).
I have a very similar case where I do the following:
get root folders for mail server (-> where parent folder is null)
for each folder get the child folders
Another solution is using Transfer Objects and you map the entities to the transfer objects so that the required structure is obtained.
It all depends on your use case which (if any) solution is appropriate. E.g. if you can execute an AJAX call each time a folder is expanded (tree structure) then the first solution is ideal.
I worked around this by replacing
<bag name="folders" table="FOLDERS" lazy="false" inverse="true" cascade="all-delete-orphan">
<key column="MAIL_SERVER_ID"></key>
<one-to-many class="test.Folder" />
</bag>
in MailServer.hbm.xml with
<one-to-one name="rootFolder" class="test.Folder" cascade="all" />
That did the trick.
For my current project I have to map a legacy database using hibernate, but I'm running into some problems.
The database is setup using one 'entity' table, which contains common properties for all domain objects. Properties include (among others) creation date, owner (user), and a primary key which is subsequently used in the tables for the domain objects.
A simple representation of the context is as such:
table entity
- int id
- varchar owner
table account
- int accountid (references entity.id)
table contact
- int contactid (references entity.id)
- int accountid (references account.accountid)
My problem exhibits itself when I try to add a collection mapping to my account mapping, containing all contacts belonging to the account. My attempts boil down to the following:
<hibernate-mapping>
<class name="Contact" table="entity">
<id name="id" column="id">
<generator class="native" />
</id>
<join table="contact">
<key column="contactid"/>
<!-- more stuff -->
</join>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="Account" table="entity">
<id name="id" column="id">
<generator class="native" />
</id>
<bag name="contacts" table="contact">
<key column="accountid" />
<one-to-many class="Contact"/>
</bag>
<join table="account">
<key column="accountid"/>
<!-- more stuff -->
</join>
</class>
</hibernate-mapping>
However, when I try to fetch the account I get an SQL error, stating that the entity table does not contain a column called accountid. I see why this is happening: the mapping tries to find the accountid column in the entity table, when I want it to look in the contact table. Am I missing something obvious here, or should I approach this problem from another direction?
This looks to me like you actually need to be mapping an inheritance, using the Table Per Subclass paradigm.
Something like this:
<class name="entity" table="entity">
<id name="id" column="id">
...
</id>
<joined-subclass name="contact" table="contact">
<key column="contactid"/>
</joined-subclass>
<joined-subclass name="account" table="account">
<key column="accountid"/>
</joined-subclass>
</class>
That's approximate by the way - it's described in detail in section 9.1.2 of the Hibernate documentation (just in case you can't find it, it's called "Table per subclass").
Cheers
Rich