If I have a mapping like this:
<class name="Users" table="users">
<id column="id" name="id">
<generator class="native"/>
</id>
...
<set name="types" table="types" cascade="all">
<key column="user_id" />
<element column="type_name" type="string" />
</set>
</class>
How should the user object be created? I did this:
User u = new User();
u.getType().add(new Type(type_name));
getHibernateTemplate().save(u);
But there will be the error java.lang.ClassCastException: Type.
The Type class only has an integer user_id and string type_name in it with get/set.
Why doesn't it work? Where can I find documentation on saving objects with collection of elements? Thanks you so much.
Have a look at http://docs.jboss.org/hibernate/stable/core/reference/en/html/collections.html.
Change the element to:
<element column="type_name" type="Type" />
Then you can add types to the set. Right now you have it defined as String.
Related
I need to handle approving records and making them avaialble based on their status. My original idea was to use a flag column to filter the records but business practices of strict segregation of approved / unapproved records prevents this approach. The next most logical approach (to me) would be to move the records to an approved table.
I am using the entity-name attribute to map the same class to two different tables - APPROVED_ and UNAPPROVED_. When I try to move the record it deletes from the unapproved but does not insert into approved. I turned on hibernate.show_sql and the retrieves / deletes are shown but no insert.
The approved table has generator class="assigned" so that it uses the id from the unapproved table as its key.
Any suggestions on what I'm not doing correctly? Or a better way to do this?
Here is the code
try {
// begin transaction
ses = Activator.getSession();
ses.beginTransaction();
dao.setSession(ses);
daoMotor.setSession(ses);
// for each input record in selectedMotors
for (Long curId : selectedMotors) {
// retrieve the input record
IThreePhaseMotorInput record = dao.findById(curId, false);
// save the motor into the permanent table using entity-name
IThreePhaseMotor curMotor = record.getMotor();
daoMotor.makePersistent("ThreePhaseMotor", (ThreePhaseMotor) curMotor);
// delete the input record
dao.makeTransient((ThreePhaseMotorInput) record);
}
// commit transaction
ses.getTransaction().commit();
} catch (Throwable t) {
ErrorInfo info = ErrorInfoFactory.getUnknownDatabaseInfo(t, null, IThreePhaseMotorList.class.getName());
Platform.getLog(Activator.getContext().getBundle()).log(
new Status(IStatus.ERROR, Activator.PLUGIN_ID, info.getErrorDescription(), t));
throw new BusinessException(info);
} finally {
if (ses != null && ses.isOpen()) {
ses.close();
}
}
And the abbreviated hbm.xml files:
<class name="ThreePhaseMotorInput" table="THREE_PHASE_MOTOR_INPUT" lazy="false">
<id name="id" type="java.lang.Long">
<column name="ID" />
<generator class="native" />
</id>
<version generated="never" name="version" type="java.lang.Integer" />
<many-to-one name="motor" cascade="all" entity-name="UnapprovedThreePhaseMotor" fetch="join">
<column name="MOTOR" />
</many-to-one>
</class>
<class name="ThreePhaseMotor" table="UNAPPROVED_THREE_PHASE_MOTOR" entity-name="UnapprovedThreePhaseMotor">
<id name="id" type="java.lang.Long">
<column name="ID" />
<generator class="native" />
</id>
<version generated="never" name="version" type="java.lang.Integer" />
</class>
<class name="ThreePhaseMotor" table="THREE_PHASE_MOTOR" entity-name="ApprovedThreePhaseMotor">
<id name="id" type="java.lang.Long">
<column name="ID" />
<generator class="assigned" />
</id>
<version generated="never" name="version" type="java.lang.Integer" />
After sleeping on it (my wire says I do some of my best thinking while sleeping!), I realized that the issue is as gkamai suggested. I need to do a deep copy.
Change
IThreePhaseMotor curMotor = record.getMotor();
daoMotor.makePersistent("ThreePhaseMotor", (ThreePhaseMotor) curMotor);
to
IThreePhaseMotor curMotor = new ThreePhaseMotor(record.getMotor());
daoMotor.makePersistent("ThreePhaseMotor", (ThreePhaseMotor) curMotor);
In my application I have a setup like the following:
class A {
String id;
List<C> list;
}
class B {
String id;
List<C> list;
}
class C {
String id;
}
which is mapped in hibernate like
<class name="A" lazy="false">
<id name="id">
<generator class="org.hibernate.id.UUIDGenerator"/>
</id>
<list name="list" lazy="false" mutable="false">
<key column="listA_id"/>
<list-index column="listA_index"/>
<many-to-many class="C"/>
</list>
</class>
<class name="B" lazy="false">
<id name="id">
<generator class="org.hibernate.id.UUIDGenerator"/>
</id>
<list name="list" lazy="false" mutable="false">
<key column="listB_id"/>
<list-index column="listB_index"/>
<many-to-many class="C"/>
</list>
</class>
<class name="C" lazy="false">
<id name="id">
<generator class="org.hibernate.id.UUIDGenerator"/>
</id>
</class>
All 5 tables (one for each class and 2 for the many-many-relations) are created as expected. The problem appears when I execute some code like the following:
A a = new A();
B b = new B();
C c1 = new C();
C c2 = new C();
C c3 = new C();
<hibernatesession>.save(c1);
<hibernatesession>.save(c2);
<hibernatesession>.save(c3);
a.list.add(c1);
a.list.add(c2);
<hibernatesession>.save(a);
b.list.add(c1);
b.list.add(c3);
<hibernatesession>.save(b);
I followed the resulting SQL in a HSQL-log-file and found everything to be fine up until the last save. Saving b will result in the association between a and c1 being deleted from the many-many-tabel A_list. Fetching the objects a and b anew from the database will result in c1 and c3 being in b.list but only c2 in a.list.
Observations:
* There is no cascading in the game.
* switching the order of saving a/b will result in c1 getting deleted from the list of whoever gets saved first.
* Both lists are mapped with indexes.
* Both lists are mapped with different key/index-column names.
* hibernate deletes exactly those elements common to the lists
Can somebody explain me this behavior of hibernate and knows how to realize a respective mapping correctly?
Thx in advance!!!
Salsolatragus
Try adding a many-to-many mapping to Class C so that it is a bidirectional relationship. Perhaps by being explicit about the "parent" relationships, it will know to keep more than one relationship rather than delete the first one as you have explained.
<class name="C" lazy="false">
<id name="id">
<generator class="org.hibernate.id.UUIDGenerator"/>
</id>
<list name="listA" lazy="false" mutable="false" inverse="true">
<key column="listC_id"/>
<many-to-many class="A"/>
</list>
<list name="listB" lazy="false" mutable="false" inverse="true">
<key column="listC_id"/>
<many-to-many class="B"/>
</list>
</class>
Fixed the bug. Lessons learned: If you have strange correlations between certain Collections in hibernate, check first if you're accidentally using the same Java instance for them... A couple of hours pairDebugging thru hibernate at last led us to the right guess. Tricky thing. Problem solved.
Thx for you hint Brad!
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'm starting my adventure with Hibernate, so please be patient :)
I want to make mapping for two tables, for example A and B. The relation beetwen A and B is one-to-many.
I wrote this hbm.xml file:
<hibernate-mapping package="something">
<class name="A" table="A">
<id name="id" type="int" column="ID">
<generator class="native" />
</id>
<set name="setInA" sort="natural" cascade="all" lazy="false">
<key column="ANOTHER_ID"/>
<one-to-many class="B" />
</set>
</class>
<class name="B" table="B">
<id name="anotherId" type="int" column="ANOTHER_ID">
<generator class="native" />
</id>
</class>
</hibernate-mapping>
Of course I made also POJO classes A and B.
And now, when I try to do:
A a = new A();
Set<B> set = new TreeSet<B>();
set.add(new B());
a.setSetInA(set);
session.save(a);
Hibernate inserts new row to table A, but (what is the worst) is not inserting new row to B table, but only makes SQL Update on not existing row in B.
Can tell me anyone why it is happening? What I made wrong?
You should either persist B's objects firstly, or use Cascade option.
You can use Cascade without using annotations:
<set name="setInA" sort="natural" cascade="all" lazy="false" cascade="all">
<key column="ANOTHER_ID"/>
<one-to-many class="B" />
</set>
This will ensure that collection of B instances is inserted when you insert A.
Found this question while searching for causes of the same symptoms in my system. cascade="all" did not help.
In my case I solved this by adding a mapping to the list element, in this example class B.
Please note that the enclosing class (A in this example) also was versioned. Hibernate might require that versioning (used for optimistic locking) must be enabled for all nested elements. I haven't found any documentation to support this theory.
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