Table of Contents

JPA - How to define a @One-to-Many relationship ?

About

To define a one-to-many relationship, the following annotation is used @OneToMany.

Syntax

The Interface with parameters and default values:

@Target({METHOD, FIELD}) @Retention(RUNTIME)
public @interface OneToMany {
   Class             targetEntity()      default void.class;
   CascadeType[]     cascade()           default {};
   FetchType         fetch()             default LAZY;
   String            mappedBy()          default "";
   boolean           orphanRemoval()     default false;
}

where:

Snippets

Bidirectional with generics

One-to-Many association using generics

@OneToMany(cascade=ALL, mappedBy="customer", orphanRemoval=true)
public Set<Order> getOrders() { return orders; }
@ManyToOne
@JoinColumn(name="CUST_ID", nullable=false)
public Customer getCustomer() { return customer; }

Bidirectional without generics

One-to-Many association without using generics

@OneToMany(
    targetEntity=com.acme.Order.class,
    cascade=ALL,
    mappedBy="customer",
    orphanRemoval=true
)
public Set getOrders() { return orders; }
@ManyToOne
@JoinColumn(name="CUST_ID", nullable=false)
protected Customer customer;

Unidirectional using a foreign key mapping

@OneToMany(orphanRemoval=true)
@JoinColumn(name="CUST_ID") // join column is in table for Order
public Set<Order> getOrders() {return orders;}

The @JoinColumn avoid to generate a join table in JPA 2.0

Steps

Data Model

The data model is the following:

HELLO_ID HELLO_DESC
1 Hello Nico !
CAT_DESC HELLO_ID
A normal Hello 1

Database

One Hello has severals hello Categories.

One To Many Hello Data Model

hello_data_model_one_to_many.ddl

Jpa

With an unidirectional metamodel model.

HELLO_CAT Table
import javax.persistence.*;
import java.io.Serializable;

/**
 * Represents an hello Category
 */
@Entity
@Table(name="HELLO_CAT")
public class HelloCategory implements Serializable {


    @Id
    @Column(name = "CAT_DESC")
    private String desc;

    @Id
    // If the join column is not define, it will default to class+id column (HELLO_WORLD_ID)
    // and you will get a  ORA-00904  invalid identifier
    // JoinColumn defines the name of the column in the table Hello_Cat
    @JoinColumn(name= "HELLO_ID")
    private HelloWorld helloWorld;

    public HelloCategory() {
    }

    public HelloCategory(HelloWorld helloWorld) {
        this.setHelloWorld(helloWorld);
    }

    public String getDesc() {
        return desc;
    }

    public void setDesc(String desc) {
        this.desc = desc;
    }

    public HelloWorld getHelloWorld() {
        return helloWorld;
    }

    public void setHelloWorld(HelloWorld helloWorld) {
        this.helloWorld = helloWorld;
    }

    @Override
    public String toString() {
        return "HelloCategory{" +
                "desc='" + desc + '\'' +
                "helloWorld="+helloWorld.getDescription()+
                '}';
    }
}
Hello Table
import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "HELLO")
public class HelloWorld implements Serializable {

    @Id
    @Column(name = "HELLO_ID", unique = true, nullable = false, updatable=false)
    private String id;

    @Basic()
    @Column(name = "HELLO_DESC")
    private String Description;

    @OneToMany(orphanRemoval=true)
    // The join column is in the many/target table (ie the HelloCategory)
    // and is mandatory in order to not create a Join table (many-to-many structure).
    @JoinColumn(name="HELLO_ID") 
    private List<HelloCategory> helloCategories = new ArrayList<HelloCategory>();

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getDescription() {
        return Description;
    }

    public void setDescription(String description) {
        Description = description;
    }


    public void add(HelloCategory helloCategory) {
        //To change body of created methods use File | Settings | File Templates.
        getHelloCategories().add(helloCategory);

    }

    public List<HelloCategory> getHelloCategories() {
        return helloCategories;
    }

    @Override
    public String toString() {
        return "HelloWorld{" +
                "id='" + id + '\'' +
                ", Description='" + Description + '\'' +
                ", helloCategories=" + helloCategories +
                '}';
    }
}

JPA Configuration and Run

With JPA EclipseLink as persistence provider.

persistence.xml

persistence.xml file

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
    <persistence-unit name="PersistenceUnitName" transaction-type="RESOURCE_LOCAL">
        <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
        <class>HelloWorld</class>
        <class>HelloCategory</class>
    </persistence-unit>
</persistence>

Main

import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.config.TargetDatabase;
import org.eclipse.persistence.logging.SessionLog;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) {

        Map props = new HashMap();


        props.put(PersistenceUnitProperties.JDBC_USER, "sh");
        props.put(PersistenceUnitProperties.JDBC_PASSWORD, "sh");
        props.put(PersistenceUnitProperties.TARGET_DATABASE, TargetDatabase.Oracle11);
        props.put(PersistenceUnitProperties.JDBC_DRIVER,"oracle.jdbc.OracleDriver");
        props.put(PersistenceUnitProperties.JDBC_URL,"jdbc:oracle:thin:@localhost:1521/pdborcl.hotitem.local");
        props.put(PersistenceUnitProperties.LOGGING_LEVEL, SessionLog.FINE_LABEL);

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersistenceUnitName", props);

        EntityManager em = emf.createEntityManager();

        // A normal Hello World Construction
        // The normal Hello World
        HelloWorld aNormalHelloWorld = new HelloWorld();
        aNormalHelloWorld.setId("1");
        aNormalHelloWorld.setDescription("Hello Nico !");
        // The normal Hello Category
        HelloCategory aNormalHelloCategory = new HelloCategory(aNormalHelloWorld);
        aNormalHelloCategory.setDesc("A normal Hello");
        aNormalHelloWorld.add(aNormalHelloCategory);


        // Persistence
        em.getTransaction().begin();
        em.merge(aNormalHelloWorld);
        em.getTransaction().commit();

        // Retrieve
        // Hello World whose primary key is 1
        HelloWorld helloWorld = em.find(HelloWorld.class, "1");
        System.out.println(helloWorld);
        for (HelloCategory helloCategory:helloWorld.getHelloCategories()){
            System.out.println(helloCategory.getDesc());
        }


        em.close();
        emf.close();

    }

}

Output

Log
[EL Config]: metadata: 2013-12-22 17:49:43.495--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The access type for the persistent class [class HelloCategory] is set to [FIELD].

[EL Config]: metadata: 2013-12-22 17:49:43.522--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The element [field helloWorld] is being defaulted to a one to one mapping.

[EL Config]: metadata: 2013-12-22 17:49:43.524--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The element [field helloWorld] is being defaulted to a one to one mapping.

[EL Config]: metadata: 2013-12-22 17:49:43.531--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The target entity (reference) class for the one to one mapping element [field helloWorld] is being defaulted to: class HelloWorld.

[EL Config]: metadata: 2013-12-22 17:49:43.532--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The access type for the persistent class [class HelloWorld] is set to [FIELD].

[EL Config]: metadata: 2013-12-22 17:49:43.538--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The target entity (reference) class for the one to many mapping element [field helloCategories] is being defaulted to: class HelloCategory.

[EL Config]: metadata: 2013-12-22 17:49:43.539--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The alias name for the entity class [class HelloCategory] is being defaulted to: HelloCategory.

[EL Config]: metadata: 2013-12-22 17:49:43.565--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The alias name for the entity class [class HelloWorld] is being defaulted to: HelloWorld.

[EL Config]: metadata: 2013-12-22 17:49:43.58--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The primary key column name for the mapping element [field helloWorld] is being defaulted to: HELLO_ID.

[EL Warning]: metadata: 2013-12-22 17:49:43.581--ServerSession(1293031597)--Thread(Thread[main,5,main])
--You have specified multiple ids for the entity class [HelloCategory] without specifying an @IdClass. By doing this you may lose the ability to find by identity, distributed cache support etc. Note: You may however use entity manager find operations by passing a list of primary key fields. Else, you will have to use JPQL queries to read your entities. For other id options see @PrimaryKey.

[EL Config]: metadata: 2013-12-22 17:49:43.598--ServerSession(1293031597)--Thread(Thread[main,5,main])
--The primary key column name for the mapping element [field helloCategories] is being defaulted to: HELLO_ID.

[EL Info]: 2013-12-22 17:49:44.238--ServerSession(1293031597)--Thread(Thread[main,5,main])
--EclipseLink, version: Eclipse Persistence Services - 2.5.2.v20131113-a7346c6

[EL Config]: connection: 2013-12-22 17:49:44.246--ServerSession(1293031597)--Connection(1340160707)--Thread(Thread[main,5,main])
--connecting(DatabaseLogin(
	platform=>Oracle11Platform
	user name=> "sh"
	datasource URL=> "jdbc:oracle:thin:@localhost:1521/pdborcl.hotitem.local"
))
[EL Config]: connection: 2013-12-22 17:49:44.717--ServerSession(1293031597)--Connection(1524017553)--Thread(Thread[main,5,main])
--Connected: jdbc:oracle:thin:@localhost:1521/pdborcl.hotitem.local
	User: SH
	Database: Oracle  Version: Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
With the Partitioning, OLAP, Advanced Analytics and Real Application Testing options
	Driver: Oracle JDBC driver  Version: 11.1.0.7.0-Production

[EL Info]: connection: 2013-12-22 17:49:44.785--ServerSession(1293031597)--Thread(Thread[main,5,main])
--file:/D:/svn_obiee-utility-plus/target/classes/_PersistenceUnitName_url=jdbc:oracle:thin:@localhost:1521/pdborcl.hotitem.local_user=sh login successful

[EL Fine]: sql: 2013-12-22 17:49:44.848--ServerSession(1293031597)--Connection(1524017553)--Thread(Thread[main,5,main])
--SELECT HELLO_ID, HELLO_DESC FROM HELLO WHERE (HELLO_ID = ?)
	bind => [1]

[EL Fine]: sql: 2013-12-22 17:49:44.988--ServerSession(1293031597)--Connection(1524017553)--Thread(Thread[main,5,main])
--SELECT CAT_DESC, HELLO_ID FROM HELLO_CAT WHERE (HELLO_ID = ?)
	bind => [1]

[EL Fine]: sql: 2013-12-22 17:49:44.996--ServerSession(1293031597)--Connection(1524017553)--Thread(Thread[main,5,main])
--SELECT CAT_DESC, HELLO_ID FROM HELLO_CAT WHERE ((CAT_DESC = ?) AND (HELLO_ID = ?))
	bind => [A normal Hello, 1]

[EL Fine]: sql: 2013-12-22 17:49:45.006--ClientSession(2069097123)--Connection(1524017553)--Thread(Thread[main,5,main])
--INSERT INTO HELLO_CAT (CAT_DESC, HELLO_ID) VALUES (?, ?)
	bind => [A normal Hello, 1]

[EL Fine]: sql: 2013-12-22 17:49:45.008--ClientSession(2069097123)--Connection(1524017553)--Thread(Thread[main,5,main])
--UPDATE HELLO_CAT SET HELLO_ID = ? WHERE ((HELLO_ID = ?) AND (CAT_DESC = ?))
	bind => [1, 1, A normal Hello]

[EL Fine]: sql: 2013-12-22 17:49:45.01--ClientSession(2069097123)--Connection(1524017553)--Thread(Thread[main,5,main])
--DELETE FROM HELLO_CAT WHERE ((CAT_DESC = ?) AND (HELLO_ID = ?))
	bind => [A normal Hello 2, 1]

System output
HelloWorld{id='1', Description='Hello Nico !', helloCategories={[HelloCategory{desc='A normal Hello'helloWorld=Hello Nico !}]}}
A normal Hello

Log
[EL Config]: connection: 2013-12-22 17:49:45.054--ServerSession(1293031597)--Connection(1524017553)--Thread(Thread[main,5,main])
--disconnect

[EL Info]: connection: 2013-12-22 17:49:45.055--ServerSession(1293031597)--Thread(Thread[main,5,main])--
file:/D:/svn_obiee-utility-plus/target/classes/_PersistenceUnitName_url=jdbc:oracle:thin:@localhost:1521/pdborcl.hotitem.local_user=sh logout successful

[EL Config]: connection: 2013-12-22 17:49:45.056--ServerSession(1293031597)--Connection(1340160707)--Thread(Thread[main,5,main])
--disconnect

Documentation / Reference