- In Java, one-to-one relationship (normally called association) is where an object has a reference (instance variable) of the other object. In JPA world, these objects are nothing but user defined entities.
- The object which has the reference is known as source object (owner of the relationship) , whereas, the object which is being referenced is known as target object.
- In this relationship, same target object instance is not shared (repeated) with different instances of the source object, otherwise it will become many-to-one relationship.
- In database world, one-to-one relationship is where a table A has a special column, known as foreign-key column, referencing to the primary key column of another table B. Table A is known as child-table, whereas, the table B is known as parent-table.
- In JPA, we map one-to-one association of objects to parent-child tables by using @OneToOne. This annotation is used in the source class and placed on the field/property of the target reference.
- The source Java entity corresponds to the child table (the one which has the foreign key column) and the target entity corresponds to the parent table.
Mapping one-to-one association
Following example demonstrates how a one-to-one Java object association is mapped to database one-to-one entity relationship.
@Entity
public class EntityA {
@Id
@GeneratedValue
private int myIdA;
@OneToOne
private EntityB entityB;
public EntityB getEntityB() {
return entityB;
}
public void setEntityB(EntityB entityB) {
this.entityB = entityB;
}
}
@Entity
public class EntityB {
@Id
@GeneratedValue
private int myIdB;
private String stringB;
public String getStringB() {
return stringB;
}
public void setStringB(String stringB) {
this.stringB = stringB;
}
}
public class ExampleMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
EntityManager em = emf.createEntityManager();
nativeQuery(em, "SHOW TABLES");
nativeQuery(em, "SHOW COLUMNS from EntityA");
nativeQuery(em, "SHOW COLUMNS from EntityB");
emf.close();
}
public static void nativeQuery(EntityManager em, String s) {
System.out.printf("---------------------------%n'%s'%n", s);
Query query = em.createNativeQuery(s);
List list = query.getResultList();
for (Object o : list) {
System.out.println(Arrays.toString((Object[]) o));
}
}
} Output--------------------------- 'SHOW TABLES' [ENTITYA, PUBLIC] [ENTITYB, PUBLIC] --------------------------- 'SHOW COLUMNS from EntityA' [MYIDA, INTEGER(10), NO, PRI, NULL] [ENTITYB_MYIDB, INTEGER(10), YES, , NULL] --------------------------- 'SHOW COLUMNS from EntityB' [MYIDB, INTEGER(10), NO, PRI, NULL] [STRINGB, VARCHAR(255), YES, , NULL]
H2 database SHOW statements
Here's a quick review of above example.
The foreign key column name
In above example, the table EntityA contains a foreign key to the table EntityB. By default, the foreign key column name is generated as the concatenation of the following: - the name of the relationship field i.e EntityA#entityB, - then "_" and - then the name of the primary key column in table EntityB.
Using @JoinColumn
If we don't want to use the default name generation of the foreign key column (as stated above) or the table already exists with different foreign-key column name then we should use @JoinColumn annotation on the field/property:
@Entity
public class EntityA {
@Id
@GeneratedValue
private int myIdA;
@OneToOne
@JoinColumn(name = "MY_JOIN_COLUMN")
private EntityB entityB;
public EntityB getEntityB() {
return entityB;
}
public void setEntityB(EntityB entityB) {
this.entityB = entityB;
}
} Complete Example
Persisting data
In above example, we focused on Java object to database table mapping. In this example we are going to populate and persist our entity objects.
public class ExampleMain2 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
EntityManager em = emf.createEntityManager();
EntityB entityB = new EntityB();
entityB.setStringB("testString");
EntityA entityA = new EntityA();
entityA.setEntityB(entityB);
em.getTransaction().begin();
em.persist(entityA);
em.persist(entityB);
em.getTransaction().commit();
ExampleMain.nativeQuery(em, "select * from EntityA");
ExampleMain.nativeQuery(em, "select * from EntityB");
emf.close();
}
} Output--------------------------- 'select * from EntityA' [1, 2] --------------------------- 'select * from EntityB' [2, testString]
Use of 'optional' element of @OneToOne
What will happen, if we persist entityA with entityB being null in above example? Let's try:
public class ExampleMain3 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
EntityManager em = emf.createEntityManager();
EntityA entityA = new EntityA();
em.getTransaction().begin();
em.persist(entityA);
em.getTransaction().commit();
ExampleMain.nativeQuery(em, "select * from EntityA");
ExampleMain.nativeQuery(em, "select * from EntityB");
emf.close();
}
} Output--------------------------- 'select * from EntityA' [1, null] --------------------------- 'select * from EntityB'
Nothing persisted in EntityB table.
@OneToOne has an element; 'optional'. By default it's value is true. If we set it to false then a non-null relationship must always exist.
Following example demonstrates that setting 'optional=false' and persisting EntityA with null reference of EntityB, will throw an exception.
@Entity
public class EntityA {
@Id
@GeneratedValue
private int myIdA;
@OneToOne(optional = false)
private EntityB entityB;
public EntityB getEntityB() {
return entityB;
}
public void setEntityB(EntityB entityB) {
this.entityB = entityB;
}
}
OutputCaused by: javax.persistence.PersistenceException: org.hibernate.PropertyValueException: not-null property references a null or transient value : com.logicbig.example.EntityA.entityB at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:147) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:155) at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:162) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:780) at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758) at com.logicbig.example.ExampleMain.main(ExampleMain.java:20) ... 6 more Caused by: org.hibernate.PropertyValueException: not-null property references a null or transient value : com.logicbig.example.EntityA.entityB at org.hibernate.engine.internal.Nullability.checkNullability(Nullability.java:92) at org.hibernate.action.internal.AbstractEntityInsertAction.nullifyTransientReferencesIfNotAlready(AbstractEntityInsertAction.java:115) at org.hibernate.action.internal.AbstractEntityInsertAction.makeEntityManaged(AbstractEntityInsertAction.java:124) at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:283) at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:258) at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:245) at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:326) at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:275) at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:182) at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:125) at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67) at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132) at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58) at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:773) ... 8 more
Complete Example
Use of 'cascade' element of @OneToOne
In our second last example (ExampleMain2), we persisted both entities by calling em.persist(entityB) and em.persist(entityA) . What will happen if we persist entityA only, will entityB be persisted automatically. Let's try:
public class ExampleMain4 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test1");
try {
EntityManager em = emf.createEntityManager();
EntityB entityB = new EntityB();
entityB.setStringB("testString");
EntityA entityA = new EntityA();
entityA.setEntityB(entityB);
em.getTransaction().begin();
em.persist(entityA);
em.getTransaction().commit();
ExampleMain.nativeQuery(em, "select * from EntityA");
ExampleMain.nativeQuery(em, "select * from EntityB");
}
finally {
emf.close();
}
}
}
Output
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance beforeQuery flushing : com.logicbig.example.EntityA.entityB -> com.logicbig.example.EntityB
at org.hibernate.engine.spi.CascadingActions$8.noCascade(CascadingActions.java:380)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:119)
at org.hibernate.event.internal.AbstractFlushingEventListener.cascadeOnFlush(AbstractFlushingEventListener.java:150)
at org.hibernate.event.internal.AbstractFlushingEventListener.prepareEntityFlushes(AbstractFlushingEventListener.java:141)
If we want to propagate operations performed by EntityManger from the entity having @OneToOne to the associated entity, then we can specify a suitable value for cascade of @OneToOne. By default no operations are cascaded, that's why we had above exception.
In following example, we want to propagate persist() operation, so we will specify cascade= Cascade Type. PERSIST
@Entity
public class EntityC {
@Id
@GeneratedValue
private int myIdC;
@OneToOne(cascade = CascadeType.PERSIST)
private EntityD entityD;
public EntityD getEntityD() {
return entityD;
}
public void setEntityD(EntityD entityD) {
this.entityD = entityD;
}
}
@Entity
public class EntityD {
@Id
@GeneratedValue
private int myIdD;
private String stringD;
public String getStringD() {
return stringD;
}
public void setStringD(String stringD) {
this.stringD = stringD;
}
}
In following code, we have to persist EntityC only, that will persist EntityD as well.
public class ExampleMain5 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("test2");
EntityManager em = emf.createEntityManager();
EntityC entityC = new EntityC();
EntityD entityD = new EntityD();
entityD.setStringD("testString");
entityC.setEntityD(entityD);
em.getTransaction().begin();
em.persist(entityC);
em.getTransaction().commit();
ExampleMain.nativeQuery(em, "select * from EntityC");
ExampleMain.nativeQuery(em, "select * from EntityD");
emf.close();
}
} Output--------------------------- 'select * from EntityC' [1, 2] --------------------------- 'select * from EntityD' [2, testString]
We will be discussing other elements of @OneToOne annotation in upcoming tutorials.
Example ProjectDependencies and Technologies Used: - h2 1.4.193: H2 Database Engine.
- hibernate-core 5.2.8.Final: The core O/RM functionality as provided by Hibernate.
Implements javax.persistence:javax.persistence-api version 2.1 - JDK 1.8
- Maven 3.3.9
|