How to structure SQL Statement to avoid so many joins - java

I am trying to build a select statement which will allow me to build a local in app cache when my application starts up. My table structure looks as follows;
I know to get some help I must demonstrate what I have already attempted but its extremely
I am using hibernate which should make things easier but I am really hitting a road block, the only approach I can think of is to select all the fields and then do a left outer join on the table ids matching, however the issue with this approach is that one user can have 0-1 business cards/ 0-1 social cards, 0-1 personal cards which means when i left outer join some of the data potentially might be missing for a user. The personalInfo/Logindetails and Device Data is all 1:1 mapping
Is there a more efficient way to do the select in hibernate which will allow me to easily construct my cache?
Ideally I would like to construct an object such that;
public class User {
private BusinessCard businessCard;
private SocialCard socialCard;
private PersonalCard personalCard;
private PersonalInformation personalInformation;
private LoginDetails loginDetails;
private DeviceData deviceData;
public BusinessCard getBusinessCard() {
return businessCard;
}
public void setBusinessCard(BusinessCard businessCard) {
this.businessCard = businessCard;
}
public SocialCard getSocialCard() {
return socialCard;
}
public void setSocialCard(SocialCard socialCard) {
this.socialCard = socialCard;
}
public PersonalCard getPersonalCard() {
return personalCard;
}
public void setPersonalCard(PersonalCard personalCard) {
this.personalCard = personalCard;
}
public PersonalInformation getPersonalInformation() {
return personalInformation;
}
public void setPersonalInformation(PersonalInformation personalInformation) {
this.personalInformation = personalInformation;
}
public LoginDetails getLoginDetails() {
return loginDetails;
}
public void setLoginDetails(LoginDetails loginDetails) {
this.loginDetails = loginDetails;
}
public DeviceData getDeviceData() {
return deviceData;
}
public void setDeviceData(DeviceData deviceData) {
this.deviceData = deviceData;
}
}
Thanks

From Hibernate Community Documentation - Charpter 7 - Association Mappings:
7.2.1. Many-to-one
A unidirectional many-to-one association is the most common kind of unidirectional association.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person (
personId bigint not null primary key
, addressId bigint not null
)
create table Address (
addressId bigint not null primary key
)
7.2.2. One-to-one
A unidirectional one-to-one association on a foreign key is almost identical. The only difference is the column unique constraint.
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
create table Person (
personId bigint not null primary key
, addressId bigint not null unique
)
create table Address (
addressId bigint not null primary key
)
This is how you may map one-to-one relationship. Notice this is the same sa many-to-one mapping, except for the unique constraint.
Now having mapped your classes, your Person class shall look like the following.
public class Person {
public Address getAddress() { return address; }
public void setAddress(Address address) { this.address = address; }
public int getId() { return id; }
protected void setId(int id) { this.id = id; }
private Address address;
private int id;
}
To answer your question
It all depends how you want the data to be persisted. As expressed, I would go with the identities of BusinessCard, SocialCard and PersonalCard into the User table so that one and only one card may exist per user.
<class name "User" table="Users">
<id name="id" column="userId">
<generator class="native" />
</id>
<many-to-one name="businessCard"
column="businessCardId"
unique="true"
not-null="true" />
<many-to-one name="socialCard"
column="socialCardId"
unique="true"
not-null="true" />
<many-to-one name="personalCard"
column="personalCardId"
unqiue="true"
not-null="true" />
</class>
<class name="BusinessCard" table="BusinessCards">
<id name="id" column="businessCardId">
<generator class="native" />
</id>
</class>
<class name="SocialCard" table="SocialCards">
<id name="id" column="socialCardId">
<generator class="native" />
</id>
</class>
<class name="PersonalCard" table="PersonalCards">
<id name="id" column="personalCardId">
<generator class="native" />
</id>
</class>

Related

How to tell Hibernate to map a one-to-many key column into derived class table?

How can I create a Hibernate mapping for a one-to-many mapping to a polymorphic class so that the foreign key is added to the child table?
The polymorphic class is mapped so that the base class has its own table and the child class has a table joined to the base class table. All my attempts add an id column and foreign key to the base table.
Java classes:
public class Base
{
private String id;
private int BaseProperty;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public int getBaseProperty()
{
return BaseProperty;
}
public void setBaseProperty(int baseProperty)
{
BaseProperty = baseProperty;
}
}
public class Child extends Base
{
private int childProperty;
public int getChildProperty()
{
return childProperty;
}
public void setChildProperty(int childProperty)
{
this.childProperty = childProperty;
}
}
public class Owner
{
private String id;
private Set<Child> children;
public String getId()
{
return id;
}
public void setId(String id)
{
this.id = id;
}
public Set<Child> getChildren()
{
return children;
}
public void setChildren(Set<Child> children)
{
this.children = children;
}
}
Hibernate mapping:
<hibernate-mapping auto-import="true" default-lazy="false">
<class name="net.opsource.oec.integration.service.sven.Owner"
table="SR_OWNER">
<id name="id" type="string" unsaved-value="null">
<column name="ID" sql-type="char(128)" not-null="true"/>
<generator class="assigned"/>
</id>
<set name="children" cascade="all">
<key column="OWNED_CHILD_ID" foreign-key="FK_CHILD2_OWNER"/>
<one-to-many class="net.opsource.oec.integration.service.sven.Child"/>
</set>
</class>
<class name="net.opsource.oec.integration.service.sven.Base"
abstract="true" table="SR_BASE"
discriminator-column="Base">
<discriminator column="JOB_TYPE" type="string" length="50"/>
<id name="id" type="string" unsaved-value="null">
<column name="ID" sql-type="char(128)" not-null="true"/>
<generator class="assigned"/>
</id>
<discriminator column="TYPE" type="string" length="50"/>
<property name="baseProperty"/>
</class>
<subclass name="net.opsource.oec.integration.service.sven.Child"
extends="net.opsource.oec.integration.service.sven.Base"
discriminator-value="Child">
<join table="SR_CHILD" fetch="select">
<key column="CHILD_ID" unique="true"
foreign-key="FK_CHILD_BASE"/>
<property name="childProperty"/>
</join>
</subclass>
</hibernate-mapping>
This generates the following schema:
create table SR_BASE (
ID char(128) not null,
TYPE varchar(50) not null,
baseProperty integer,
OWNED_CHILD_ID varchar(255),
primary key (ID)
) ENGINE=InnoDB;
create table SR_CHILD (
CHILD_ID varchar(255) not null,
childProperty integer,
primary key (CHILD_ID)
) ENGINE=InnoDB;
create table SR_OWNER (
ID char(128) not null,
primary key (ID)
) ENGINE=InnoDB;
How can I tell Hibernate to create the OWNED_CHILD_ID column and its key in the SR_CHILD table instead?
We are using Hibernate 4.2.
Thanks / Sven

Hibernate parent/child association. Update parent make child lost all associated objects

I have following classes.
public class Employee {
private int id;
private String firstName;
private String lastName;
private Address address;
private Employer employer;
}
public class Employer {
private int id;
private String name;
private Set<Employee> employees;
}
public class Address {
private int id;
}
public class Project{
private int id;
}
<hibernate-mapping>
<class name="me.hibernate.basic.xml.Employee" table="EMPLOYEE">
<id name="id" type="int" column="id">
<generator class="native" />
</id>
<property name="firstName" column="first_name" type="string" />
<property name="lastName" column="last_name" type="string" />
<many-to-one name="address" column="address" unique="true" class="me.hibernate.basic.xml.Address"/>
<set name="projects" cascade="save-update, delete-orphan" sort="natural">
<key column="employee_proj_id" />
<one-to-many class="me.hibernate.basic.xml.Project" />
</set>
<many-to-one name="employer" column="employer" class="me.hibernate.basic.xml.Employer"/>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="me.hibernate.basic.xml.Employer" table="EMPLOYER">
<id name="id" type="int" column="id">
<generator class="native" />
</id>
<property name="name" column="name" type="string" />
<set name="employees" cascade="save-update, delete-orphan" table="EMPLOYEE">
<key column="employer"/>
<one-to-many class="me.hibernate.basic.xml.Employee" />
</set>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="me.hibernate.basic.xml.Address" table="ADDRESS">
<meta attribute="class-description"> This class contains the address detail. </meta>
<id name="id" type="int" column="id">
<generator class="native" />
</id>
</class>
</hibernate-mapping>
<hibernate-mapping>
<class name="me.hibernate.basic.xml.Project" table="PROJECT">
<meta attribute="class-description"> This class contains the project detail. </meta>
<id name="id" type="int" column="id">
<generator class="native" />
</id>
</class>
</hibernate-mapping>
In my application I create few Employees and assign an address to them. And add some projects. Then I create an employer and add all the employees to employer. Once I add employees and update the employer, all the employees lose their addresses and projects. How can I do this with keeping lazy loading feature. I don't need to set lazy="false".
Employee emp1 = ME.addEmployee("Zara", "Ali");
Employee emp2 = ME.addEmployee("Daisy", "Das");
Address addr1 = ME.addAddress(35, "xxxxxx Street", "XXXXX", "XY7 0ZZ");
Address addr2 = ME.addAddress(42, "xxxxxx Street", "XXXXX", "XY7 7ZZ");
ME.setAddress(emp1.getId(), addr1);
ME.setAddress(emp2.getId(), addr2);
Set<Project> proj = new HashSet<Project>();
proj.add(new Project("NOVA"));
proj.add(new Project("GTA Simplify"));
proj.add(new Project("Jazz"));
ME.addProjects(emp.getId(), proj);
ME.addProjects(emp.getId(), proj);
All working up to this point.
Set<Employee> emps = new HashSet<Employee>();
emps.add(emp1); emps.add(emp2);
//Add existing employees to employer - Many-to-one bidirectional
Employer employer = ME.addEmployer("XYZ");
ME.addEmployees(employer.getId(), emps);
public Integer addEmployees(Integer employerID, Set<Employee> employees) {
Session session = factory.openSession();
Transaction tx = null;
try {
tx = session.beginTransaction();
Employer employer = (Employer) session.get(Employer.class,employerID);
employer.getEmployees().clear();
employer.getEmployees().addAll(employees);
session.update(employer);
tx.commit();
} catch (HibernateException e) {
if (tx != null)
tx.rollback();
e.printStackTrace();
} finally {
session.close();
}
return employerID;
}
Once I added employees, all the foreign key references are lost in the PROJECT.employee_proj_id and EMPLOYEE.address.
Hibernate log:
->Hibernate: update EMPLOYEE set first_name=?, last_name=?, basic=?, address=?, employer=? where id=?
->Hibernate: update EMPLOYEE set first_name=?, last_name=?, basic=?, address=?, employer=? where id=?
->Hibernate: update PROJECT set employee_proj_id=null where employee_proj_id=?
->Hibernate: update PROJECT set employee_proj_id=null where employee_proj_id=?
->Hibernate: update EMPLOYEE set employer=? where id=? ->Hibernate: update EMPLOYEE set employer=? where id=?
I see that you are clearing the employees before you add more employees, which is the root cause of everything. I'm not sure what you are doing when adding addresses, Project, employer etc., You need not clear the employees. If you have cascadetype.all set on the employees then saving the employer will update the newly added employees.
employer.getEmployees().clear();//remove this line.
employer.getEmployees().addAll(employees);

Why am I getting Mapping Exception?

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>

can we map non-object propety to an object in Many-to-one relationship of hibernate?

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.

Hibernate: How to select a single item based on a foreign key column that is part of an association mapping?

Hullo,
So... I have the following objects:
public class Person {
// some other getters/setters omitted.
void setAddress(Address addy) {
// omitted
}
Address getAddress() {
// omitted
}
}
public class Address {
Integer getId() {
// omitted
}
}
And, I have the following hibernate mappings:
<class name="Person">
<id name="id" column="personId">
<generator class="native"/>
</id>
<many-to-one name="address"
column="addressId"
unique="true"
not-null="true"/>
</class>
<class name="Address">
<id name="id" column="addressId">
<generator class="native"/>
</id>
</class>
So, there is a one to one mapping from Person to Address, Person has the foreign key to Address.
What I'm trying to do is fetch a Person object from a given Address ID... but I can't seem to figure out the correct HQL syntax:
public Person getPersonFromAddress(Address address) {
Query query = this.session.createQuery("select p from Person as p where p.address_id = " + address.getId());
#SuppressWarnings("unchecked")
Person p = (Person)query.uniqueResult();
return p;
}
I know I don't have a mapping from the foreign key column to a property on Person. Everytime I try to add one I get an exception saying I'm using the same column twice? I don't see what would be wrong with that :).
Anyways, what would be the best way to fetch the Person given an Address? Any help would be appreciated.
You should be able to do this
Query query = this.session.createQuery("select p from Person as p where p.address=:address")
.setParameter("address",address);

Categories

Resources