I am trying to persist a Class with a LinkedList Attribute but can't seem to get it right. Here is my code and my mapping:
import java.util.LinkedList;
public class Stuff implements java.io.Serializable {
private long id;
private LinkedList<Image> images;
public Stuff() {
}
public Stuff(long Id) {
id = Id;
}
public long getId() {
return id;
}
public void setId(long mealId) {
id = mealId;
}
public LinkedList<Image> getNumberImages(int number) {
assert (number >= 0);
return (LinkedList<Image>) images.subList(0, number) ;
}
public LinkedList<Image> getImages() {
return images;
}
public LinkedList<Image> setImages(LinkedList<Image> images) {
this.images = images;
}
public void addImage(Image image) {
if (!images.contains(image)) {
images.add(image);
}
}
Hibernate mapping:
<hibernate-mapping>
<class name="data.Stuff" table="Stuff">
<id name="id" type="long" access="field">
<column name="ID" />
<generator class="assigned" />
</id>
<list name="images" inverse="false" table="IMAGE" lazy="true" access="field">
<key>
<column name="ID" />
</key>
<list-index></list-index>
<one-to-many class="data.Image" />
</list>
</class>
</hibernate-mapping>
It seems I can persist objects of the Class Stuff like this but when I try to recover them the following error occurres :
Hibernate: select stuff0_.ID as ID0_, stuff0_.NAME as NAME0_, meal0_.GROUPING as GROUPING0_ from MEAL meal0_
org.hibernate.PropertyAccessException: could not set a field value by reflection setter of data.Meal.images
Generally, Hibernate will provide its own implementations for collections so you should prefer interfaces to specific implementations. It's probably attempting to assign a different kind of list to images and failing. You would have to change your field to List<Image>.
Hibernate (and JPA in general) persists collections using their interfaces. The list must be declared as a List, and not as a LinkedList. And it won't be loaded with a LinkedList instance, because Hibernate uses its own List implementation to implement dirty-checking, lazy-loading, etc.
It's a good practice to program on interfaces rather than programming on concrete implementations in general. In JPA entities, it's mandatory.
Related
I have the following tables:
The following classes:
public class Parent {
private int id;
private Set<Child> children;
// getters and setters
}
public class Child {
private int id;
private String attr;
// getters and setters
public int hashCode() {
return this.getId();
}
public boolean equals(Object obj) {
return obj instanceof Child && this.getId() == ((Child)obj).getId();
}
}
And the following Hibernate mapping:
<class name="Parent" table="parent">
<id name="id" column="id"/>
<set name="children" table="child">
<key column="id_parent"/>
<composite-element class="Child">
<property name="id" column="id"/>
<property name="attr" column="attr"/>
</composite-element>
</set>
</class>
Whenever I change attr attribute from a Child or add/remove a Child to/from the collection and commit the transaction, the entire collection is deleted and the remaining children are reinserted with the new attribute values.
Other topics discuss this, and the guys always blame equals() and hashCode() or the fact the collection is a list or bag. But in this very simple case I don't think they are the problem.
Is this because I'm using an entity (Parent) and a component class (Child) instead of using two entities in a one-to-many relationship?
I am getting :
org.hibernate.MappingException: Foreign key (FKBB979BF4266AA123:address [a_id]))
must have same number of columns as the referenced
primary key (address [p_id,a_id])
as I try to run the following (though incomplete) snippet :
public static void main(String args[]) {
Configuration config = new Configuration().configure();
SessionFactory sessFact = config.buildSessionFactory();
Session sess = sessFact.openSession();
Transaction trans = sess.beginTransaction();
}
The hibernate mapping xml is shown below :
<class name="pojo.Person" table="person">
<id column="p_id" name="personID">
<generator class="increment" />
</id>
<property name="personName" column="p_name" />
<set name="addressSet" table="address" cascade="all">
<key column="p_id" />
<many-to-many class="pojo.Address" column="a_id" />
</set>
</class>
<class name="pojo.Address" table="address">
<id column="a_id" name="addressID">
<generator class="foreign" />
</id>
<property name="address" column="address" />
</class>
I am trying a many to many association between Person and Address class.
What is the reason for this exception ?
I have created two tables person and address using these sql commands :
CREATE TABLE person(p_id INTEGER,p_name TEXT,PRIMARY KEY(p_id));
CREATE TABLE address(a_id INTEGER,address TEXT);
POJO
Person
public class Person {
private int personID;
private String personName;
private Set addressSet;
public int getPersonID() {
return personID;
}
public void setPersonID(int personID) {
this.personID = personID;
}
public String getPersonName() {
return personName;
}
public void setPersonName(String personName) {
this.personName = personName;
}
public Set getAddressSet() {
return addressSet;
}
public void setAddressSet(Set addressSet) {
this.addressSet = addressSet;
}
Address
public class Address {
private int addressID;
private String address;
private Set personSet;
public int getAddressID() {
return addressID;
}
public void setAddressID(int addressID) {
this.addressID = addressID;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Set getPersonSet() {
return personSet;
}
public void setPersonSet(Set personSet) {
this.personSet = personSet;
}
}
For a ManyToMany Relationshhip you need a dedicated mapping table
6.2.4. Collections of values and many-to-many associations
i.e. You need something like a PersonAddress Table
CREATE TABLE personaddress (p_id integer, a_id integer)
Where p_id is a FK Reference to the Person Table and a_id a FK Reference to the Address Table
You need to specify different table name for many-to-many association as it's handled by a separate table:
<class name="pojo.Person" table="person">
<id column="p_id" name="personID">
<generator class="increment" />
</id>
<property name="personName" column="p_name" />
<set name="addressSet" table="person_address" cascade="all">
<key column="p_id" />
<many-to-many class="pojo.Address" column="a_id" />
</set>
</class>
Note that <set> now references to person_addresses table. With default configuration, Hibernate is able to create it automatically.
There's another mistake that I see: ID generator for Address entity should not be foreign, it's usually used in 1-to-1 relationships (uses ID of another associated object). You can use the same 'increment' use used for Person entity:
<class name="Address" table="address">
<id column="a_id" name="addressID">
<generator class="increment" />
</id>
<property name="address" column="address" />
</class>
You need to create a reference table:
CREATE TABLE PersonsAddresses (personId BIGINT, addressId BIGINT)
and change the mapping of the set to this:
<set name="addressSet" table="PersonsAddresses" order-by="personId">
<key column="personId" foreign-key="p_id"/>
<many-to-many column="addressId" node="a_id" class="pojo.Address" />
</set>
I have a very specific scenario as follow.
public class Person
{
Long id;
Collection<PersonRelation> personRelationCollection = new LinkedHashSet<PersonRelation>();
/**
has respective getter and setter
**/
}
public class PersonRelation
{
Long id;
Long parentPersonId; // here I don't want parentPersonId of type Person
Long childPersonId; // here also I don't want childPersonId of type Person
String relationType;
/**
has respective getter setter
**/
}
In my mapping files I have following
<class name="Person" table="PERSON">
<id name="id" column="IDENTIFIER">
<generator class="native"/>
</id>
<set
name="personRelationCollection"
table="PERSON_RELATION"
cascade="all"
>
<key column="PARENT_PERSON_ID"/>
<one-to-many class="PersonRelation"/>
</set>
</class>
and
<class name="PersonRelation" table="PERSON_RELATION">
<id name="id" column="IDENTIFIER">
<generator class="native"/>
</id>
<!-- following many-to-one mapping doesn't work-->
<!-- I need help here to satisfy my requirement -->
<many-to-one
name="parentPersonId"
column="PARENT_PERSON_ID"
class="Person"
not-null="true"/>
<Property name="childPersonId" column="CHILD_PERSON_ID"/>
<property name="relationType" column="RELATION_TYPE"/>
</class>
In this example, as in PersonRelation class, attribute parentPersonId is Long and not type of Person, I'm getting
org.hibernate.MappingException: Association references unmapped class PersonRelation
$
Please help.
Forget about references by id. In Hibernate you work with objects, not tables.
I guess your code could be written like this:
#Entity
#Table(name="your_table")
public class Item{
private Long id;
private Item parentItem;
private List<Item> children;
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getId(){
}
#ManyToOne()//Your options
public Item getParentItem(){
}
#OneToMane(mappedBy="parentItem")
public List<Item> getChildren(){
}
//Setters omitted
}
finally i found answer my own. Very small thing we have to do as follow.
<class name="PersonRelation" table="PERSON_RELATION">
<id name="id" column="IDENTIFIER">
<generator class="native"/>
</id>
<!-- here remove many-to-one mapping ---- it's not needed-->
<!-- treet participantPersonId as a simple property and everything will work -->
<Property name="parentPersonId" column="PARENT_PERSON_ID" type="Long"/>
<Property name="childPersonId" column="CHILD_PERSON_ID"/>
<property name="relationType" column="RELATION_TYPE"/>
</class>
This works perfectly fine. :)
Here, when you insert Person object, then it will not inset PersonRelation object too. You have to explicitly insert PersonRelation object. Perhaps, when we retrieve Person object, then it will gives you collection of PersonRelation. Here no need to retrieve PersonRelation collection explicitly.
I'm trying to map an order and its order items in Hibernate. An order item should not be able to reference its parent order:
public class Order {
private long id;
private Set<OrderIter> orderItems = new HashSet<OrderItem>();
public long id() {
return id;
}
public void add(OrderItem item) {
item.setItemNumber(orderItems.size() + 1);
orderItems.add(item);
}
public Set<OrderItem> orderItems() {
return Sets.newHashSet(orderItems);
}
}
public class OrderItem {
private int itemNumber;
public int itemNumber() {
return itemNumber;
}
public void setItemNumber(int itemNumber) {
this.itemNumber = itemNumber;
}
}
The objective is to have Hibernate automatically persist an order item when it's added to an order, like this:
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
Order order = (Order) session.load(Order.class, orderId);
OrderItem item = new OrderItem();
order.add(item);
// Done
session.getTransaction().commit();
HibernateUtil.getSessionFactory().close();
I looked at Chapter 24. Example: Parent/Child, but in this example the child has a reference to the parent. I'm now trying to map it using Collections of dependent objects:
<class name="Order" table="ORDERS">
<id name="id" column="ORDER_ID" access="field">
<generator class="native" />
</id>
<set name="orders" table="ORDER_ITEMS" access="field">
<key column="id" />
<composite-element class="OrderItem">
<property name="ItemNumber" access="field" />
</composite-element>
</set>
</class>
This is almost working, but the combination of order id and item number should be unique. How can I meet all these criteria with a Hibernate mapping?
Here the one-to-many association between Order->OrderItem is mapped using a JOIN TABLE.
The one-to-many association is mapped with a many-to-many with unique set to true.
(since one-to-many is not aware of a join table on the set)
<class name="Order" table="ORDERS">
<set name="orders" table="ORDER_ORDERITEMS_RT">
<key column="ORDER_ID" />
<many-to-many name="OrderItem" unique="true" column="ORDERITEM_ID"/>
</set>
</class>
<class name="OrderItem table="ORDERITEMS">
</class>
The above mapping satisfies
OrderItem not having a reference to Order. As the mapping are in a separate table
One-to-many association makes the orderid-orderitemid pair unique.
You can put appropriate cascading on the set to allow saving the orderItem when added to the list on the Order. (Not shown in the mapping)
Hope this helps.
I am running into an issue with JAXRS / JAXB including child IDs in JSON results when using #Produces. Below are portions of my code. Since we are using Hibernate, I'm abstracting the id into an AbstractEntity class.
POJOs:
#XmlRootElement
public abstract class AbstractEntity implements Serializable {
private Serializable id;
#XmlElement(type-Object.class)
#XmlSchemaType(name="anySimpleType")
public final Serializable getId() {
return this.id
}
public final Serializable setId(Serializable id) {
this.id = id;
}
}
#XmlRootElement
public class Parent extends AbstractEntity {
private String parentName;
private Child child;
#XmlElement
public String getParentName() {
return parentName;
}
#XmlElement
public Child getChild() {
return child;
}
}
#XmlRootElement
public class Child extends AbstractEntity {
private String childName;
#XmlElement
public String getChildName() {
return childName;
}
}
JAXRS Services:
#Path("/parent")
public class ParentService {
#GET
#Path("/get/{id}")
#Produces(MediaType.APPLICATION.JSON)
public Parent getById(#PathParam("id") Long id) {
Parent parent = hibernateDataController.getParentById(id);
if (parent== null)
throw new NotFoundException("GET: Parent" + id + " not found.");
return parent;
}
}
#Path("/child")
public class ChildService {
#GET
#Path("/get/{id}")
#Produces(MediaType.APPLICATION.JSON)
public Child getById(#PathParam("id") Long id) {
Child child = hibernateDataController.getChildById(id);
if (child == null)
throw new NotFoundException("GET: Child " + id + " not found.");
return child;
}
}
(Note: There is more code not shown, but the main parts are above)
My project is in Eclipse, using Maven, so I fire up Jetty: mvn jetty:run
This is where the problem starts. I can access the child POJO using:
http://myserver:8080/example/child/get/1 returns->
{
"id":{"#type":"xs:long","$":"1"},
"childName":"Bart Simpson"
}
But, when I access the parent POJO, the id of the child POJO is not returned:
http://myserver:8080/example/parent/get/1 returns ->
{
"id":{"#type":"xs:long","$":"1"},
"parentName":"Homer Simpson",
"child": {
"childName":"BartSimpson"
}
}
Notice that the ID of the child is not returned, just the childName. The GUI team that I am working with is using GWT and they are requesting that I include the ID of any children in the JSON results.
Any help in getting JAXRS / JAXB to return the ID within the child JSON would be greatly appreciated. Thanks for your time.
Matt
I found my solution to the missing child IDs this morning. It actually was not a JAXRS / JAXB issue, but caused by the Hibernate mapping files (yes, I still like to use a mapping file over annotations for Hibernate).
The Hibernate mapping file for the example file above would be:
<hibernate-mapping>
<class name="com.mycompany.Parent" table="PARENT">
<id name="id" type="java.lang.Long">
<column name="PARENT_ID" scale="0" />
<generator class="native" />
</id>
<property name="parentName" type="java.lang.String">
<column name="PARENT_NAME" />
</property>
<set name="children" inverse="true" lazy="true" table="CHILD" fetch="select">
<key>
<column name="CHILD_ID" />
</key>
<one-to-many class="com.mycompany.Child" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="com.mycompany.Child" table="CHILD">
<id name="id" type="java.lang.Long">
<column name="CHILD_ID" scale="0" />
<generator class="native" />
</id>
<property name="childName" type="java.lang.String">
<column name="CHILD_NAME" />
</property>
<many-to-one name="parent" type="com.mycompany.Child" fetch="select">
<column name="PARENT_ID" />
</many-to-one>
</class>
</hibernate-mapping>
The fix was to force Hibernate not to 'lazy load' the children. I changed:
<set name="children" inverse="true" **lazy="true"** table="CHILD" **fetch="select"**>
to:
<set name="children" inverse="true" lazy="false" table="CHILD" **fetch="join"**>
With the modified hibernate mapping files, the IDs came through the results of JAXRS:
{
"id":{"#type":"xs:long","$":"1"},
"parentName":"Homer Simpson",
"child": {
"id":{"#type":"xs:long","$":"1"},
"childName":"BartSimpson"
}
}
Hope this helps if someone else runs into this issue.