How to make hibernate unidirectional many-to-many association updateable? - java

I have two entities - Category and Attribute. Category can have multiple related attributes, and Attribute can be related to any number of categories. Association should be available only on Category side - Attribute objects are not aware of categories they are related to.
So I model this association as unidirectional many-to-many:
Category.hbm.xml
<class name="Category" table="category" proxy="ICategory" entity-name="category">
<id name="id" column="id" unsaved-value="null"><generator class="identity" /></id>
...some properties...
<bag name="relatedAttributes" table="category_attribute" fetch="select">
<key column="id_category" />
<many-to-many column="id_attribute" entity-name="attribute" />
</bag>
</class>
and Attribute.hbm.xml
<class name="Attribute" table="attribute" proxy="IAttribute" entity-name="attribute">
<id name="id" column="id" unsaved-value="null" ><generator class="identity" /></id>
...some properties...
</class>
Mapping works perfectly with current data until it needs an update. I just want to do things as simple as these:
ICategory c = (ICategory) session.get("category", 1);
c.getRelatedAttributes().add((IAttribute) session.get("attribute", 2));
session.update("category", c);
How can i make this association updateable?

Finally done. Changes that affect behavior:
<bag name="relatedAttributes" table="category_attribute" fetch="select" inverse="false" cascade="save-update">
...
</bag>
and do not forget to call session.flush() after operations.

Related

Associate set mapping against not primary key column

I have two objets like:
public class Object1 implements Serializable {
protected Integer id; // This is the PK in the xml mapping
protected Integer otherId;
}
public class Object2 implements Serializable {
protected Integer id; // This is the PK in the xml mapping
protected Set<Object1> object1List; // I want to relate this set against the "otherId" attribute
}
And I have this hibernate XML mappings:
<class name="Object1" table="Object1Table">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<property name="otherId" type="java.lang.Integer">
<column name="other_id"></column>
</property>
</class>
<class name="Object2" table="Object2Table">
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="identity" />
</id>
<set name="object1List" table="Object1Table" lazy="false" fetch="join" >
<key column="other_id" /> // This is not working
<one-to-many class="Object1" />
</set>
</class>
But I don't know how to relate the "set" mapping against the "other_id" attribute, it only works against the PK "id" of the "Object1" table.
Does anyone know how to solve this situation?
I think you need to mark the <set as inverse. Like crizzis mentioned, you'd be better off if you use the annotation model. A lot more people can help you with the annotation model. Apart from that, the HBM XML mapping will be replaced with the JPA orm.xml mapping at some point, so I wouldn't use that approach anymore.

'Repeated column in mapping' when mapping a class to an existing list of objects

I'm adding ResourcePermission to the object Report. Each Query object can have in a one-to-one relationship. A Query extends Resource and has ResourcePermission's in a one-to-many (one Query to many Permissions).
I need to add the same property to the Report object associated with the Query because it can have different permissions. When I add the list and the map the one Query to many Permission relationship I get
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: com.bio.ResourcePermission column: resource_id (should be mapped with insert="false" update="false")
Which I am not understanding why, the Report does not extend Query or Resource and therefore it isn't mapped twice. Can a table just not be the many for multiple one-to-many relationships?
<class name="com.bio.Report" table="REPORT">
<id name="id" type="long" column="id">
<generator class="foreign">
<param name="property">query</param>
</generator>
</id>
<property name="name" column="name"/>
<!--Trying to add this list mapping breaks it-->
<bag name="permissions" table="RESOURCE_PERMISSION">
<key column="resource_id" not-null="true"/>
<one-to-many class="com.bio.ResourcePermission"/>
</bag>
<!-- This query extends Resource-->
<one-to-one name="query" class="com.bio.Query" />
</class>
This is the original item that had ResourcePermissions
<class name="com.bio.Resource" table="RESOURCE">
<id name="id" type="long" column="id">
<generator class="native">
<param name="sequence">SEQ_RESOURCE_AUTO</param>
</generator>
</id>
<bag name="permissions" table="RESOURCE_PERMISSION" lazy="true" batch-size="50" cascade="all-delete-orphan">
<key column="resource_id" not-null="true"/>
<one-to-many class="com.bio.ResourcePermission"/>
</bag>
</class>
The Permission mapping
<class name="com.bio.ResourcePermission" table="RESOURCE_PERMISSION">
<id name="id" type="long" column="id">
<generator class="native">
<param name="sequence">SEQ_RES_PERM_AUTO</param>
</generator>
</id>
<property name="canEdit" column="edit"/>
<property name="canView" column="can_view"/>
<property name="canRun" column="run"/>
<property name="everyone" column="everyone"/>
</class>
I had to set inverse="true" on the Report mapping since the ReportPermission will be responsible for the relationship.
<bag name="permissions" table="RESOURCE_PERMISSION" inverse="true">
<key column="resource_id" not-null="true"/>
<one-to-many class="com.bio.ResourcePermission"/>
</bag>

lazy="false" not working in hibernate 4.3.x

I am trying to test the functioning of lazy loading in hibernate with the following code.
Query query = session.createQuery("From Publishers as publisher where publisher.publisherName <= :name");
query.setString("name", "penguin");
Publishers publisher = (Publishers)query.iterate().next();
Set<Books> booksByPublisher = new HashSet<>();
booksByPublisher = publisher.getBooks();
session.close();
System.out.println(publisher.getPublisherName());
for(Books book : booksByPublisher) {
System.out.println(book);
}
mapping for publishers and books classes are as following, respectively:
<class name="newapplication.domain.Publishers" table="PUBLISHERS" >
<id name="publisherId" type="integer" column="PUBLISHER_ID"/>
<property name="publisherName" type="string" column="PUBLISHER_NAME"/>
<set name="books" table="BOOKS_PUBLISHERS" inverse="true" fetch="join" lazy="false">
<key column="publisher"/>
<many-to-many class="newapplication.domain.Books" column="book" />
</set>
</class>
<class name="newapplication.domain.Books" table="BOOKS" >
<id name="bookId" type="integer" column="BOOK_ID">
<generator class="sequence">
<param name="sequence">BOOKIDSEQUENCE</param>
</generator>
</id>
<property name="bookName" type="string" column="NAME"/>
<many-to-one name="author" class="newapplication.domain.Authors" column="AUTHOR"/>
<many-to-one name="cover" class="newapplication.domain.Covers" column="COVER"/>
<set name="publishers" table="BOOKS_PUBLISHERS" lazy="false">
<key column="BOOK"/>
<many-to-many class="newapplication.domain.Publishers" column="PUBLISHER"/>
</set>
</class>
Despite having set lazy="false" loading in the publisher class i get the following error.
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
What is wrong here? Shouldn't the Set be loaded as soon as the database is hit for the publishers' object?
Your many-to-one associations in Books are lazy; that's where the exception comes from.
By default, all associations are lazy when using Hibernate native mappings, as opposed to JPA standard (when using JPA-style mappings) where to-one associations are eager by default.

Making join with Criteria

I'm terribly new to Hibernate. I've googled for two hours but I still can't figure out, how to make JOIN without using HQL, only by criteria. I have tables Clients(cID, name) and Visits(vID, vcID, date). The relation is one to many (one client can visit multiple times). I would also like to do it without setFetchMode. Just Criteria. Do I have to change the mappping xml?
UPDATE:
this is part of my mapping xml:
<class name="Client" table="Clients">
<id name="cID" column="cID"><generator class="native"/></id>
<property name="name" length="10" not-null="true"/>
</class>
<class name="Visit" table="Visits">
<id name="vID" column="vID"><generator class="native"/></id>
<property name="vcID" length="10" not-null="true"/>
<property name="date" length="25" not-null="true"/>
</class>
Having a class Client with a list-attribute "visits" that's mapping to your Visit-Entity:
Criteria criteria = session.createCriteria(Client.class);
criteria.addCriteria("visits");
This would create an inner join between your client-table and your visits-table.
Update:
Here you'll find some good examples: http://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html#querycriteria-associations
Mapping Example
I hardly ever use hibernate mapping xml, however it should read similiar to:
<class name="Client" table="Clients">
<id name="cID" column="cID"><generator class="native"/></id>
<property name="name" length="10" not-null="true"/>
<bag name="visits">
<key column="vcId"/>
<one-to-many class="Visit"/>
</bag>
</class>
Tell Hibernate that there is a property "visits" which represents a one-to-many relationship.
You need to update you mapping:
<class name="Client" table="Clients">
<id name="cID" column="cID"><generator class="native"/></id>
<property name="name" length="10" not-null="true"/>
<!-- Declare Set<Visit> visits in the Client class-->
<set name="visits" lazy="false" cascade="all">
<key column="vcID"/>
<one-to-many class="your.package.Visit"/>
</set>
</class>
<class name="Visit" table="Visits">
<id name="vID" column="vID"><generator class="native"/></id>
<!-- and add "Client client" property to your Visit class -->
<many-to-one name="client" column="vcID" lazy="false"/>
<property name="date" length="25" not-null="true"/>
</class>
Then:
Criteria criteria = session.createCriteria(Visit.class).addCriteria("client")
.add(Restriction.eq(...));
or
Criteria criteria = session.createCriteria(Client.class).addCriteria("visits")
.add(Restriction.eq(...));
And Hibernate will join them automatically.

Hibernate many-to-many mapping and cascade=delete

I have a mapping (only important parts):
<class name="xyz.Role" table="ROLE" lazy="true">
<id name="id" type="java.lang.Integer">
<column name="ROLE_ID"/>
<generator class="increment"/>
</id>
<set name="assignments" lazy="true" table="PERSON_ROLE" cascade="delete"
inverse="true">
<key column="ROLE_ID" />
<many-to-many class="xyz.Person" column="PERSON_ID" />
</set>
</class>
and
<class name="xyz.Person" table="PERSON" lazy="true">
<id name="Id" type="java.lang.Integer">
<column name="TPP_ID"/>
<generator class="increment"/>
</id>
<set name="roles" lazy="true" table="PERSON_ROLE" cascade="save-update">
<key column="PERSON_ID" />
<many-to-many class="xyz.Role" column="ROLE_ID" />
</set>
</class>
With this mapping, when I delete a role, very person with this role is also deleted. What I would like to achieve, is delete the association (row from PERSON_ROLE table) when I delete Role. Is there any way to achieve this?
Cascade works on the level of entities. Since Person_Role is not mapped as entity, cascade can not help you AFAIK.
You might use a database-level "on cascade delete" on the foreign key from Person_Role to Role.
Or you can - as sfussenegger points out - remove the association programatically. Note that since you mapped the association on both entities, every row in Person_Role will appear twice in your object model. In such cases, it is recommended to remove the relevant entries from both collections in order not to corrupt your object model. Hibernate however will only look at the end of the association that is not mapped with inverse="true" when persisting changes. That is, to delete from the association with your current mapping, you must delete from Person.roles, not Role.assignments:
for (Person p : role.assignments) {
person.roles.remove(role)
}
Or you might wish to replace the many-to-many mapping with an association entity, in which case you could simply use cascade. This would allow you to add more information to assignments more easily. For instance, if you had to express "Joe works 30% on QA and 70% as requirements engineer", you could simply add that field to the association.
Why not simply call role.getAssignments().clear() before deleting the role?
for (Person p : role.getAssignments()) {
person.getRoles.remove(role)
}
role.getAssignments.clear();
session.delete(role);
I'm not a big fan of cascade="delete". I get this weird gut feeling any time I think about a snippet of XML being able to delete entire tables of valuable data :)
Don't put a mapping on the Role's side at all. If you eventually need this information, get it with an HQL query. I.e. get rid of this:
<set name="assignments" lazy="true" table="PERSON_ROLE" cascade="delete"
inverse="true">
<key column="ROLE_ID" />
<many-to-many class="xyz.Person" column="PERSON_ID" />
</set>
And whenever you need the persons for a role (which I think should be rare) get it by:
SELECT p FROM Person p JOIN p.roles WHERE role=:role

Categories

Resources