Let say I have a many-to-many relation in spring-data-jpa between Post entity and Post_Tag entity.
Now if I persist a post with tags like java, testing. The post_tags java, testing will be persisted with the post with a cascade type of persist. Now if I save another post with tags like php, testing, will the testing post_tag row duplicated in the Post_Tag table? Or the previous entry will be used?
It depend upon id. If you have field tagName in Post_Tag entity and used it as id then only it will use previous entry else it will create a duplicate entry.
<id name="tagName" type="string">
<column name="tag_name" />
<generator class="assigned" />
</id>
Related
I am new to Hibernate and was writing some test program.
I am wondering if its a must to have a table , one column of which will be updated using some kind of sequence.
For ex. I created a table
create table course(course_name varchar2(20));
and when I am defining Course.hbm.xml in the following way
<class name="Course" table="COURSE" >
<property name="course">
<column name="course"/>
</property>
</class>
I am getting an error in the XML file saying a declaration of "id" or something similar is expected. I can give the whole error message if required.
You need an ID column so hibernate can identify that row in the table. I'm not fluent in that oldschool hibernate xml mapping but it should look roughly like that:
create table course(id integer primary key, course_name varchar2(20));
<class name="Course" table="COURSE" >
<id name="id">
<!-- uses sequence, auto increment or whatever your DBMS uses for id generation -->
<generator class="native"/>
</id>
<property name="course">
<column name="course"/>
</property>
</class>
As a side note: mapping your entities with annotations is a bit more common nowadays. Makes it easier, especially for starters.
I'm new with Apache Cayenne.
I have only one Entity, called Product.
This entity has a many-to-many relationship with itself, that is a product can contain products, and it can be contained by other products.
I can't model this relationship with Cayenne..
What I do is:
1) I create a table called Composition, with two fields that are both PKs and FKs.
2) I create two toMany from Product to Composition (one from product.id to Composition.contained_id, and one from product.id to Composition.base_id)
This should work with the DB
Now I create only one ObjEntity: Product.
But.. How can I create a flattened relationship?? I'm following this: http://cayenne.apache.org/doc/cayennemodeler-flattened-relationships.html but maybe because it is a relationship with itself I cannot select an Entity in "Target" combo box..
Thank you
Francesco
EDIT: the target checkbox problem there is also if the two entities are different. Cayenne Modeler v.3.0.2
"Target" combo is empty when you select the first relationship, simply because there's no ObjEntity for the join table. But if you keep selecting the next path component, "Product" will appear in the combobox. I wish we redesign this UI for better clarity, but it still works now. See the DataMap XML sample below. I just created it with 3.0.2 Modeler.
Hope this helps.
<?xml version="1.0" encoding="utf-8"?>
<data-map xmlns="http://cayenne.apache.org/schema/3.0/modelMap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://cayenne.apache.org/schema/3.0/modelMap http://cayenne.apache.org/schema/3.0/modelMap.xsd"
project-version="3.0.0.1">
<db-entity name="composition">
<db-attribute name="BASE_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
<db-attribute name="CONTAINED_ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
</db-entity>
<db-entity name="product">
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true" isMandatory="true"/>
<db-attribute name="NAME" type="VARCHAR" length="255"/>
</db-entity>
<obj-entity name="Product" dbEntityName="product">
<obj-attribute name="name" type="java.lang.String" db-attribute-path="NAME"/>
</obj-entity>
<db-relationship name="base" source="composition" target="product" toMany="false">
<db-attribute-pair source="BASE_ID" target="ID"/>
</db-relationship>
<db-relationship name="contained" source="composition" target="product" toMany="false">
<db-attribute-pair source="CONTAINED_ID" target="ID"/>
</db-relationship>
<db-relationship name="base" source="product" target="composition" toDependentPK="true" toMany="true">
<db-attribute-pair source="ID" target="BASE_ID"/>
</db-relationship>
<db-relationship name="contained" source="product" target="composition" toDependentPK="true" toMany="true">
<db-attribute-pair source="ID" target="CONTAINED_ID"/>
</db-relationship>
<obj-relationship name="base" source="Product" target="Product" deleteRule="Deny" db-relationship-path="contained.base"/>
<obj-relationship name="contained" source="Product" target="Product" deleteRule="Deny" db-relationship-path="base.contained"/>
</data-map>
I have an Account entity and an AccountTransaction entity.
Account 1 <----> n AccountTransaction
In my AccountTransaction.hbm.xml I specify a many-to-one relationship:
<hibernate-mapping>
<class name="com.walshk.accountmanager.domain.AccountTransaction" table="AccountTransaction">
<id name="id" type="long" column="transaction_id">
<generator class="increment"/>
</id>
<property name="date" not-null="true" type="date" column="transaction_date"/>
<property name="description" not-null="true" column="transaction_description" length="500"/>
<property name="amount" column="transaction_amount" not-null="true"/>
<many-to-one name="account" column="account_id" not-null="true" cascade="all" lazy="false"/>
</class>
</hibernate-mapping>
This allows me to lookup AccountTransactions by account using
Criteria criteria = session.createCriteria(AccountTransaction.class)
.add(Restrictions.eq("account", account));
and also allows me to get the Account instance using AccountTransaction#getAccount();
What I want to do now is provide a way to get an account, e.g
Criteria criteria = session.createCriteria(Account.class).add(Restrictions.eq("id", id));
But I also want the Account entity to have a method
List<AccountTransaction> getTransactions();
And I want this to be lazy loaded, since I may not even need to list the transactions.
Since I am already specifying the relationship as many-to-one from the AccountTransaction how do I now specify a one-to-many relationship giving me access from the other direction.
Additionally, what's the best way to handle lazy-loading, do I have to assign a session to each entity and not close the session? I could potentially have too many sessions open though.
Thanks.
If you add a One-to-Many association in your Account class hibernate mapping, you will get:
List<AccountTransaction> getTransactions();
from any ORM creation tool.
One of the parameters of this association is the loading type - I am not familiar with the exact syntax in XML mapping, as we use annotations, but you could probably find it in any reference/documentation page of hibernate XML mapping.
in Order to work with Lazy Loading, You should have Open Session in view enabled.
If you are using Spring integration that you have OpenSesionInViewIntereptor/OpenSessionInViewFilter
If you are using native Hibernate without Spring integration, then you can implement it by yourself.
Please read the following:
http://community.jboss.org/wiki/OpenSessioninView
Hope it helps.
Hibernate mapping question where the behavior is ambiguous and/or dangerous. I have a one-to-many relationship that has a cascade-delete-orphan condition AND a where condition to limit the items in the collection. Mapping here -
<hibernate-mapping>
<class name="User" table="user" >
<!-- properties and id ... -->
<set table="email" inverse="true" cascade="all,delete-orphan" where="deleted!=true">
<key column="user_id">
<one-to-many class="Email"/>
</set>
</class>
</hibernate-mapping>
Now suppose that that I have a User object which is associated to one or more Email objects, at least one of which has a 'true' value for the deleted property. Which of the following two will happen when I call session.delete() on the User object?
The User and all the Email objects, including those with deleted=true, are deleted
The User and the Email objects that are deleted!=null are deleted.
On one hand, scenario 1) ignores the where condition, which may not be correct according to the domain model. BUT in scenario 2) if the parent is deleted, and there's a foreign key constraint on the child (email) table's join key, then the delete command will fail. Which happens and why? Is this just another example of how Hibernate's features can be ambiguous?
I didn't test the mapping but in my opinion, the correct (default) behavior should be to ignore the where condition and to delete all the child records (that's the only option to avoid FK constraints violations when deleting the parent). That's maybe not "correct" from a business point of view but the other option is not "correct" either as it just doesn't work.
To sum up, the mapping itself looks incoherent. You should either not cascade the delete operation (and handle the deletion of the child Email manually).
Or, and I think that this might be the most correct behavior, you should implement a soft delete of both the User and associated Email. Something like this:
<hibernate-mapping>
<class name="User" table="user" where="deleted<>'1'">
<!-- properties and id ... -->
<set table="email" inverse="true" cascade="all,delete-orphan" where="deleted<>'1'">
<key column="user_id">
<one-to-many class="Email"/>
</set>
<sql-delete>UPDATE user SET deleted = '1' WHERE id = ?</sql-delete>
</class>
<class name="Email" table="email" where="deleted<>'1'">
<!-- properties and id ... -->
<sql-delete>UPDATE email SET deleted = '1' WHERE id = ?</sql-delete>
</class>
</hibernate-mapping>
What is done here:
We override the default delete using sql-delete to update a flag instead of a real delete (the soft delete).
We filter the entities and the association(s) using the where to only fetch entities that haven't been soft deleted.
This is inspired by Soft deletes using Hibernate annotations. Not tested though.
References
5.1.3. Class
6.2. Collection mappings
16.3. Custom SQL for create, update and delete
I have a user object that has a one-to-many relationship with String types. I believe they are simple mappings. The types table hold the associated user_id and variable type names, with a primary key 'id' that is basically a counter.
<class name="Users" table="users">
<id column="id" name="id" />
...
<set name="types" table="types" cascade="save-update">
<key column="id" />
<one-to-many class="Types" />
</set>
</class>
<class name="Types" table="types">
<id column="id" name="id" />
<property column="user_id" name="user_id" type="integer" />
<property column="type" name="type" type="string" />
</class>
This is the java I used for adding to the database:
User u = new User();
u.setId(user_id);
...
Collection<Types> t = new HashSet<Types>();
t.add(new Type(auto_incremented_id, user_id, type_name));
u.setTypes(t);
getHibernateTemplate().saveOrUpdate(u);
When I run it, it gives this error:
61468 [http-8080-3] WARN org.hibernate.util.JDBCExceptionReporter - SQL Error: 1062, SQLState: 23000
61468 [http-8080-3] ERROR org.hibernate.util.JDBCExceptionReporter - Duplicate entry '6' for key 'PRIMARY'
61468 [http-8080-3] ERROR org.hibernate.event.def.AbstractFlushingEventListener - Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
When I check the sql, it shows:
Hibernate: insert into users (name, id) values (?, ?)
Hibernate: insert into types (user_id, type, id) values (?, ?, ?)
Hibernate: update types set id=? where id=?
Why does Hibernate try to update the types' id?
The error says: Duplicate entry '6' for key 'PRIMARY', but there really isn't? I made sure the ids are incremented each time. And the users and types are added into the database correctly.
I logged the information going in, and the types added has an id of 7 and a user id of 6. Could it be that Hibernate takes the user_id of 6 and tried to update types and set id=6 where id=7? Therefore the duplicate primary key error?
But why would it do something so strange? Is there a way to stop it from updating?
Should I set the id manually? If not, then how should I add the types? It gives other errors when I add a type object that only has a type string in it and no ids.
Thanks guys. Been mulling over it for days...
Your biggest problem is incorrect column in the <key> mapping - it should be "user_id", not "id". That said, your whole mapping seems a bit strange to me.
First of all, if you want IDs auto generated you should really let Hibernate take care of that by specifying appropriate generator:
<id column="id" name="id">
<generator class="native"/>
</id>
Read Hibernate Documentation on generators for various available options.
Secondly, if all you need is a set of string types, consider re-mapping them into a collection of elements rather than one-to-many relationship:
<set name="types" table="types">
<key column="user_id"/>
<element column="type" type="string"/>
</set>
That way you won't need explicit "Types" class or mapping for it. Even if you do want to have additional attributes on "Types", you can still map it as component rather than entity.
Finally, if "Types" must be an entity due to some requirement you have not described, the relationship between "Users" and "Types" is bi-directional and needs to be mapped as such:
<set name="types" table="types" inverse="true">
<key column="user_id"/>
<one-to-many class="Types"/>
</set>
...
in Types mapping:
<many-to-one name="user" column="user_id" not-null="true"/>
In the latter case "Types" would have to have a "user" property of type "Users".
Here is a detailed example.
The solution that worked for me was to separately save the two parts (without adding type to user):
getHibernateTemplate().save(user);
getHibernateTemplate().save(new Type(user.id, type_name));
And with the < generator class="native" /> on only the types id.
Bad practice?
Had mapped it as a collection of elements, but it was somewhat wrongly adding the user_id to the types id column which causes duplicate error sometimes; since types id is the only primary key column. Weird! I sort of remember there was some other column error, but forgot about it, because I immediately reverted back to one-to-many. Couldn't grasp the strange workings of Hibernate...
I will try the bi-directional solution sometime... Thanks a lot for all the help :)