Why #OneToOne is allowing duplicate associations? - jpa

I have User and Address classes as follows:
class User
{
...
...
#OneToOne( cascade=CascadeType.ALL)
#JoinColumn(name="addr_id")
private Address address;
}
class Address
{
...
...
#OneToOne(mappedBy="address")
private User user;
}
Here my assumption is with this domain model I can associate one address to only one user(user.addr_id should be unique).
But with this domain model, I am able to create multiple users and associate them to the same address.
User u1 = new User();
//set data
Address addr1 = new Address();
//set data
u1.setAddress(addr1);
...
session.save(u1);
--> this is creating one address record with addr_id=1 and inserting one user record in user table with addr_id=1. Fine.
Again,
User u2 = new User();
//set data
Address addr1 = (Address) session.load(Address.class, 1);
//set data
u2.setAddress(addr1);
...
session.save(u2);
--> This is creating second user record and associating to existing address with addr_id=1, which is not desired.
I can use #OneToOne(..)#JoinColumn(name="addr_id", unique=true) to prevent this.
But what will be the difference of using #OneToOne rather than #ManyToOne(.., unique=true).
#OneToOne itself should impose "unique=true" condition..right?
-Siva

#OneToOne is annotating the Java to express the idea of the relationship between the two classes. If this was a #OneToMany then we'd have a collection instead. So reading the annotations we understand the realtionships, and the JPA runtime also understands those.
The actual policing of the one-to-one is performed in the database - we need the schema to have the uniqueness constraints. The #JoinColumn expresses how that relationship is manifest in the DatabaseSchema.This can be useful if we are generating the schema.
However in many cases we use bottom-up tools to generate the Java from the schema. In this case there's no actual need for the Java annotations to reflect the database constraints, from a Java perspective we just see the relationship.
An intelligent compiler might warn us if the semantics of our #JoinColumn doesn't match the #oneToOne, but I'm not sure whether current implementations do that.

OneToOne is an annotation describing the object model. It says what the cardinality of the association is. unique="true" is an indication to the "model-to-ddl" tool that there should be a unique cosntraint on this column. You can use such a tool, but you're also free to create and maintain the database schema yourself.
Whatever you choose to do, the JPA engine doesn't make a query each time the OneToOne association is modified to check that the cardinality is respected. All it does is assuming that the code is correct, and the unique constraint in the database will make sure that the cardinality is respected.

Related

Composite key with JPA entity, implementing tree of objects in one table?

I have one table named PLACES with one composite primary key (parent_id, version_id). It is a tree of objects, which are linked through the keys. One child has just one parent, and one parent may have many children.
How can I describe it with JPA entity?
Use a ManyToOne relation from the child to the parent.
This is for OpenJpa. Might even work.
public class Place{
#EmbeddedId
PlaceId id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="PARENT_ID" referencedColumnName="ID"), // ID = matching primary key
#JoinColumn(name="PARENT_VER" referencedColumnName="VER") //etc
})
public Place parent;
#OneToMany(fetch=FetchType.LAZY, mappedBy="parent")
public List<Place> childPlaces;
}
The OneToMany relation might be omitted if it's not needed. If I remember correctly, it needs to be managed, ie childs need to be inserted there too when creating child-places, by you, using java.
Btw.
I would advise against using a version column in a composite key in order to manually keep old versions of your data (for auditing or similar purposes) as that slows down and complicates all joins, and generally will make you miserable at some point in your life - As opposed to using a version column that is not part of a composite key, used for optimistic locking.
You might want to look into some kind of build in support for auditing/logging. OpenJpa has auditing support (OpenJPA Audit) and most database provide some support, either out-of-the-box or by using triggers. All alternatives are faster and better than using composite keys.

JPA Multiple relationships on one field in an entity

I am a beginner to using JPA 2.0 and databases in general and I was just confused about a few concepts.
So I have a total of 3 tables. One is the UserTable, which contains all the information about my user. It has a primary key field called user_Id. My other two tables are ExercisesTable and FoodIntakeTable, and they each have a foreign key field called user_Id to reference the user_Id in my UserTable. I want a one-to-many relationship from my user_Id table to each of the two tables so I can find pull out exercise information or food information for a user.
Pretty much like this:
FoodIntakeTable <-> UserTable <-> ExercisesTable
I need a bidirectional mapping from UserTable to FoodIntakeTable and also a bidirectional mapping from UserTable to ExercisesTable from the field user_Id.
The problem is, when I try to write my code like this in my Usertable class:
#OneToMany(mappedBy="ExercisesTable.userId")
#OneToMany(mappedBy="FoodIntakeTable.userId")
public long userId;
It's illegal because I can't have two #OneToMany annotations on the same field. I think it's supposed to be legal in a normal relational database and I'm just confused about how you translate this into JPA. I'm very new to the whole concept of databases and entities in general, so any help would be appreciated.
In JPA you can directly reference entity objects instead of the ids that they are mapped by. Try something like this:
You should have an entity type for each of your tables, say Exercise for ExercisesTable, FoodIntake for FoodIntakeTable, and User for your UserTable.
Then your User entity is the owning side of the relationships, having one field per relationship like this:
#OneToMany(mappedBy=...)
private List<Exercise> exercises;
#OneToMany(mappedBy=...)
private List<FoodIntake> foodIntakes;

How to optionally persist secondary table in Eclipselink

I am working with Eclipselink and having issue with using secondary table.
I have two tables as below.
Student with columns student_id(Primary Key), student_name etc.
Registration with columns student_id(FK relationship with Student table), course_name (with not null constraint) etc.
The requirement is student may or may not have registration. If student has registration, the data should be persisted to Registration table as well. Otherwise only Student table should be persisted.
My code snippet is as below.
Student.java
------------
#Entity
#Table(name = "STUDENT")
#SecondaryTable(name = "REGISTRATION")
#Id
#Column(name = "STUDENT_ID")
private long studentId;
#Basic(optional=true)
#Column(name = "COURSE_NAME", table = "REGISTRATION")
private String courseName;
I tried the following scenarios.
1. Student with registration - Working fine. Data is added to both Student and Registration tables
2. Student without registration - Getting error such as 'COURSE_NAME' cannot be null.
Is there a way to prevent persisting into secondary table?
Any help is much appreciated.
Thanks!!!
As #Eelke states, the best solution is to define two classes and a OneToOne relationship.
Potentially you could also use inheritance, having a Student and a RegisteredStudent that adds the additional table. But the relationship is a much better design.
It‘s possible using a DescriptorEventListener. The aboutToInsert and aboutToUpdate callbacks have access to the DatabaseCalls and may even remove the statements hitting the secondary table.
Register the DescriptorEventListener with the ClassDescriptor of the entity. For registration use a DescriptorCustomizer specified in a Customizer annotation at the entity.
However, you will not succeed fetching the entities back again later on. EclipseLink uses inner joins when selecting from the secondary table, so that the row of the primary table will be gone in the results.

JPA 2.0 retrieve entity by business key

I know there have been a number of similar posts about this, but I couldn't find a clear answer to my problem.
To make it as simple as possible, say I have such an entity:
#Entity
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
/* getters and setters */
/*
override equals() and hashCode()
to use the **name** field
*/
}
So, id is the PK and name is the business key.
Say that I get a list of names, with possible duplicates, which I want to store.
If I simply create one object per name, and let JPA make it persistent, my final table will contain duplicate names - Not acceptable.
My question is what you think is the best approach, considering the alternatives I describe here below and (especially welcome) your own.
Possible solution 1: check the entity manager
Before creating a new person object, check if one with the same person name is already managed.
Problem: The entity manager can only be queried by PK. IS there any workaround Idon't know about?
Possible solution 2: find objects by query
Query query = em.createQuery("SELECT p FROM Person p WHERE p.name = ...");
List<Person> list = query.getResultList();
Questions: Should the objects requested be already loaded in the em, will this still fetch from database? If so, I suppose it would still be not very efficient if done very frequently, due to parsing the query?
Possible solution 3: keep a separate dictionary
This is possible because equals() and hashCode() are overridden to use the field name.
Map<String,Person> personDict = new HashMap<String,Person>();
for(String n : incomingNames) {
Person p = personDict.get(n);
if (p == null) {
p = new Person();
p.setName(n);
em.persist(p);
personDict.put(n,p);
}
// do something with it
}
Problem 1: Wasting memory for large collections, as this is essentially what the entity manager does (not quite though!)
Problem 2: Suppose that I have a more complex schema, and that after the initial writing my application gets closed, started again, and needs to re-load the database. If all tables are loaded explicitly into the em, then I can easily re-populate the dictionaries (one per entity), but if I use lazy fetch and/or cascade read, then it's not so easy.
I started recently with JPA (I use EclipseLink), so perhaps I am missing something fundamental here, because this issue seems to boil down to a very common usage pattern.
Please enlighten me!
The best solution which I can think of is pretty simple, use a Unique Constraint
#Entity
#UniqueConstraint(columnNames="name")
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
}
The only way to ensure that the field can be used (correctly) as a key is to create a unique constraint on it. You can do this using #UniqueConstraint(columnNames="name") or using #Column(unique = true).
Upon trying to insert a duplicate key the EntityManager (actually, the DB) will throw an exception. This scenario is also true for a manually set primary key.
The only way to prevent the exception is to do a select on the key and check if it exists.

ADO.NET Entity Data Model: Auto-generated classes

I have a table structure like the following:
Companies Addresses
********* *********
ID ID
AddressID ...
BillingAddressID ...
AddressID and BillingAddressID are foreign keys which are present in the Addresses table. When I generate my model based on this table instead of getting what I would expect to get (the AddressID, BillingAddressID) in the company class. I get the following:
public Addresses Addresses { .. }
public global::System.Data.Objects.DataClasses.EntityReference<Addresses> AddressesReference { .. }
public Addresses Addresses1 { .. }
public global::System.Data.Objects.DataClasses.EntityReference<Addresses> Addresses1Reference { .. }
It seems to be replacing BillingAddress with Addresses1 (not quite sure why that's happening). Also this seems to be common wherever I have a foreign key i.e. instead of the ID I get Table then the TableReference.
I think I can see whats happening i.e. instead of giving me the ID alone, it will be doing a lookup and finding the actual record the ID refers to. However, I am not quite sure what the TableReference field is for....
Can explain this a little better for me?
Thanks in advance.
Relationships are represented as objects in Entity Framework, in the same manner as entities. Even if you are not going to work a lot directly on them, relationship object are first class citizens in EF. EF kreates ObjectStateEntry objects for tracking changes on relationships, just like it does it for entities.
That is why there are two references. First one, AddressesReference is a reference to the relationship object, not the exact entity, and second one Addresses is actual entity.
Peter Chan (link), and Julia Lerman in her book Programming Entity Framework, 1st Edition, say that understanding how relationship works in EF is very important. Also they mention that this is first thing that is confusing developer when they start using EF.
The foreign keys are replaced by a reference to the entity (collection) the foreign key points to.
So to add an address to a company you would do something like:
Address a = new Address();
// ... set variables for address here
currentCompany.Addresses = a;
// or, the other way round (for collections)
a.Companies.Add(currentCompany);
EF uses the table names as the reference point when it builds the model and this is why you see "Addresses" and Addresses1". You can open up the entity model in the GUI format and click on each of the associations. These can be renamed to whatever you like, just click on the reference, view the mapping, ensure it is the one that maps "BillingAddressID" to "BillingAddressID" and rename that reference to "BillingAddress".
Note the current "Addresses" reference may be the one mapping the "BillingAddressID" so you have to check both references.
It would probably be best to change the mapping for "AddressID" to be "Address" instead of "Addresses" if it is a one to one mapping as well.