JPA #OneToOne with same type - java

How to annotate my code to have a Person with 2 Addresses :
#Entity
public Person {
// ... other attributes for a person
#OneToOne
public Address homeAddress;
#OneToOne
public Address workAddress;
}
#Entity
public Address {
// ... other attributes for an address
#OneToOne
public Person person;
}
Can I use OneToOne ?
Should I have to use options on this annotations ?

Unfortunately this is not possible to achieve with #OneToOne. The reason:
the persistence provider will have one Person id for two entries the Address table. This is not sufficient to decide which relation a given Address belongs to.
The simplest solution would be to add a type field (an enum) to the Address entity and map the addresses with #OneToMany/#ManyToOne.
In order to get the home address, you would need to iterate over the addresses and check for type.
Alternatively, you could create extra types like HomeAddress and WorkAddress which would derive from the Address. You could then keep the #OneToOne relations, but would end up with two additional types.
IMO a cleaner entity relation mapping is not a sufficient reason for doing this, as you are inviting some issues. For example a HomeAddress can never be a WorkAddress.
EDIT: If both Address ids are stored in the Person table, you should be able to use the#OneToOne relation. To ensure deletion of attached Address entities and deletion of orphaned Address entities, you can use cascading and orphan removal:
#OneToOne(cascade=CascadeType.ALL, orphanRemoval=true)
Although it might look like this makes sure that there could be no orphaned Address records in the DB, it is not entirely true. Orphan removal works only when you remove the referenced entity inside a transaction while the entities are attached. Furthermore it does not work for bulk updates. A DELETE FROM Person WHERE ... query will happily delete the Persons and will not touch the connected Addresses.

OneToOne implies a table has a foreign key to another, but you haven't specified which, and are implying that it isn't a real 1:1 situation from address->person. Will employee have a workAddress_ID and homeAddress_id field? In which case, there are two different 1:1s. What isn't valid is your address->Employee 1:1 as there is no way for it to use both the workAddress_ID and homeAddress_id relationships. You could work around this by having address have 2 OneToOnes that are private, and then a public getPerson method used by the application that returns the one that isn't null. Setting the person would require looking at the passed in person object ot know which of the Address 1:1's to populate, but it wouldn't matter as much since they wouldn't control the relationship:
public Address {
// ... other attributes for an address
#OneToOne(mappedby="workAddress")
private Person workPerson;
#OneToOne(mappedby="homeAddress")
private Person homePerson;
public Person getPerson() {
return workPerson==null? homePerson:workPerson;
}
public void setPerson(Person p) {
workPerson=null;
homePerson=null;
if (p !=null) {
if (p.getHomeAddress()==this) {
homePerson=p;
} else {
workPerson=p;
}
}
}
}

Related

JPA Design of User and Roles

I have a User class and a Role class. Both of these classes are JPA entities, and hence stored in a Person table and a Role table, as well as a corresponding link table Person_Role used for joins, since the association is many to many. A user may have many roles, and a role may be assigned to many users.
#Entity
#Table(name="role")
public class Role implements Comparable<Role>
{
// data members
#Id #GeneratedValue
private int id; // primary key
private String name; // name of the role
private String description; // description of the role
...
}
#Entity
#Table(name="person")
public class Person implements Comparable<Person>
{
// data members
#Id #GeneratedValue
protected int id; // the primary key
protected String username; // the user's unique user name
protected String firstName; // the user's first name
protected String lastName; // the user's last name
protected String email; // the user's work e-mail address
#Transient
protected String history; // chronological list of changes to the person
// don't want to load this unless an explicit call to getHistory() is made
#Transient
protected Set<Role> roles; // list of roles assigned to the user
// don't want to load this unless an explicit call to getRoles() is made
...
}
The User entity is used extensively throughout the application, as it is a shared reference for many objects, and is used in many, many searches. 99.99% of the time, the user's roles and history are not needed. I'm new to JPA, and have been reading the "Java Persistence with Hibernate" book in order to learn. As I understand lazy fetching, it will load all the corresponding User data from the database when any getXXX() method is called.
Ex: user.getFirstName() would cause a database hit and load all the data, including roles and history, for the user.
I want to avoid this at all costs. Its just needless in 99.99% of the use cases. So, what's the best way to handle this?
My initial thought is to mark the Set<Role> roles and Set<String> history in the User class as #Transient and manually query for the roles and history only when the user.getRoles() or user.getHistory() method is called.
Thanks for any suggestions.
As I understand lazy fetching, it will load all the corresponding User
data from the database when any getXXX() method is called.
You can force JPA to be eager or lazy while fetching data from the database but first and foremost it depends on JPA provider. As described in JPA 2.1 specification, chapter 11.1.6:
The FetchType enum defines strategies for fetching data from the
database:
public enum FetchType { LAZY, EAGER };
The EAGER strategy is a requirement on the persistence provider
runtime that data must be eagerly fetched. The LAZY strategy is a
hint to the persistence provider runtime that data should be fetched lazily when it is first accessed. The implementation is permitted to
eagerly fetch data for which the LAZY strategy hint has been
specified. In particular, lazy fetching might only be available for
Basic mappings for which property-based access is used.
A nice presentation on how fetching strategies work and how performant they are in real-life scenarios you can find here.
Ex: user.getFirstName() would cause a database hit and load all the
data, including roles and history, for the user.
Data are retrieved either directly from the persistence context (usually it has a short lifespan) or indirectly from the underlying database (when it's not found in the transactional/shared caches). If entity manager is requested to get your entity object and it does not exist in the persistence context it needs to go deeper - into the database in the worst scenario.
I want to avoid this at all costs. Its just needless in 99.99% of the
use cases. So, what's the best way to handle this?
An example approach:
#Entity
#NamedQuery(name="Person.getNameById",
query="SELECT p.name FROM Person p WHERE p.id = :id")
public class Person
{
#Id #GeneratedValue
protected int id;
private String name; //the sole attribute to be requested
#ManyToMany //fetch type is lazy by default
#JoinTable
protected Set<Role> roles; //not loaded until referenced or accessed
...
}
Usually the best way to go is the find method. It's perfect when you want to retrieve all non-relationship attributes at once:
Person p = em.find(Person.class, id)
An alternative for you would be to use named query. It's useful when you need a single attribute or a small subset of attributes:
String name = em.createNamedQuery("Person.getNameById", String.class)
.setParameter("id", id)
.getSingleResult()
My initial thought is to mark the Set roles and Set history in the
User class as #Transient and manually query for the roles and history
only when the user.getRoles() or user.getHistory() method is called.
Transient attributes are not persisted in a database. Whatever you will set to these attributes it will stay in memory only. I would prefer JPA doing it lazily.
It will not Load all the data just the the relative to
Person entity = (Person) this.em.find(Person.class, id);
in lazy fetching it will issue a select statement from only the table person, as for protected Set<Role> roles;it will not be loaded but replaced with a proxy object
Hibernate uses a proxy object to implement lazy loading. When we request to load the Object from the database, and the fetched Object has a reference to another concrete object, Hibernate returns a proxy instead of the concrete associated object.
Hibernate creates a proxy object using bytecode instrumentation (provided by javassist). Hibernate creates a subclass of our entity class at runtime using the code generation library and replaces the actual object with the newly created proxy.

Have JPA perform what is actually "on delete set null"

When manually writing a database schema, it is possible to set a foreign relation cascade to "on delete set null".
I've searched Stackoverflow and google for the answer but I can't find it: how do you get JPA to do the same?
So, when I set a relation with #OneToOne, #OneToMany or #ManyToMany, what CascadeType achieves this, or is it something else I need to do?
As an example, take the objects House and Person. A house can optionally have a person owning it. But suppose the owner dies. The House instance should still exist, but the "owner" field(of type Person) should simply be set to null.
Can this be done automatically with JPA?
One possible way, I think, is using one of entity lifecycle callbacks.
class Person {
#PreRemove
protected void makeHouseOnSale() {
if (owning!= null) {
owning.owner = null;
}
}
#OneToOne
private House owning;
}
class House {
#OneToOne(
//optional = true; // default
)
private Person owner;
}
When you remove(kill) a Person instance in JTA session, #PreRemove annotated methods are invoked and those two entities commits when the session ends.

How to persist two entities at the same time with #OneToOne relationship?

I have two entities Travel and Assurance with #OneToOne relationship. Both entities must be created via the same interface with a Save botton. I use this method:
ManagedBean.java:
public String add(){
newTravel = manager.createTravel(arrivalDate, returnDate, lengthToStay, addToStay, visitPurpose);
newAssurance = manager.createAssurance(company, assuranceStart, assuranceEnd, newTravel);
return "Travellers";
}
In the database, I found the Travel_Id associated to the Assurance but The Assurance_Id is null in the Travels Table.
It seems that your relationship is not bilateral (i.e you do not use the mappedBy annotation property). If you used one, you would have only one column (either Travel_Id or Assurance_Id, depending where you put the mappedBy).
Also consider doing the saving inside the same transaction, e.g by using the same manager method and setting both side of the relationship.

How to model a User having multiple Addresses?

I have a User entity. Each User can have one or more personal Addresses. According to the Hibernate documentation for mapping embeddable collections this is how it should be done:
#Entity
public class User {
[...]
public String getLastname() { ...}
#ElementCollection
#CollectionTable(name="Addresses", joinColumns=#JoinColumn(name="user_id"))
#AttributeOverrides({
#AttributeOverride(name="street1", column=#Column(name="fld_street"))
})
public Set<Address> getAddresses() { ... }
}
#Embeddable
public class Address {
public String getStreet1() {...}
[...]
}
Now if I want the User to have a collection for work addresses too, what should I do?
Here is what I thought:
Create 2 different collections for work addresses and personal addresses then map them into 2 different tables. ( Sounds like over complicating things as both addresses are exactly the same)
Store both addresses into the same table. (However, I don't know how will I differentiate between them)
Introduce a look up entity/value object and use it somehow to differentiate between personal and work addresses. (from the database point of view we will have a look up table for address types linked via a foreign key to the address table, but I don't know how that should be modeled in the domain itself using Hibernate)
Alternative approaches are very welcomed.
Try using hibernate single table inheritance mapping:
#Entity
#Inheritance(strategy= InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="addresstype",
discriminatorType= DiscriminatorType.STRING)
public abstract class Address {
... common attributes here
}
#Entity
#DiscriminatorValue("home")
public class HomeAddress extends Address {
...
}
#Entity
#DiscriminatorValue("work")
public class WorkAddress extends Address {
...
}
And then create two collections, one for home addresses and the other for work addresses.
If there is code that is valid only for home addresses then we use the HomeAddress type, the same if there is code that is only valid for work addresses we use WorkAddress.
If there is code that is valid for both address types then we use the Address super type.

Update of of Composite Entity fails

I have Person entity which has composition with Location Entity
#ManyToOne(fetch = FetchType.EAGER, cascade =
{ CascadeType.PERSIST, CascadeType.MERGE })
#Cascade(
{org.hibernate.annotations.CascadeType.SAVE_UPDATE })
public Location getLocation()
{
return location;
}
And Location Entity has Name as Id
#Id
public String getName()
{
return name;
}
I am getting following Exception when Person's location is changed from L1 to L2 in Spring MVC form where this Person entity is modelAttribute for the form.
org.springframework.orm.hibernate3.HibernateSystemException:identifier of an instance of com.x.y.z.Location was altered from L2 to L1; nested exception is org.hibernate.HibernateException: identifier of an instance of com.x.y.z.Location was altered from L2 to L1
You're confusing Composition with Association.
What you have mapped is an association; composition in Hibernate (JPA) is mapped via #Embeddable / #Embedded annotations. Associations are relationships between separate entities; they are usually connected via entity identifiers (foreign keys in the database).
In your particular case, Person entity points to Location entity which means in the database PERSONS table has a LOCATION_ID foreign key (names may differ) to LOCATIONS table. What you're trying to do is to update that key on Location end which is illegal because it would sever Hibernate's relationship (the other end still holds the previous key value internally).
Primary keys should generally be surrogate and not updatable to begin with; if you do need to "update" it you'll have to either disassociate Location from Person, update Location and assign it to Person again OR create a brand new Location instance and assign that to your Person.
All that said, if you're really trying to model Composition relationship, you need to replace #ManyToOne with #Embedded and change your table schema accordingly. Here's a link to
Hibernate Annotations documentation on mapping components.
Also, specifying cascade types in two separate annotations (JPA vs Hibernate extension) is not a good thing. If you really need the Hibernate extension one (which you don't in this case), just use it and leave cascade attribute in JPA annotations empty.
I done same thing in standalone application . The thing works. I think it should be some problem with #modelAttribute.
In your Location entity attribute id type has been changed in your model class.Please refer the id and mapping attribute id types are same.Make sure that id attribute getter and setter function return types.

Categories