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

1 - About

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

3 - 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:

  • targetEntity is the entity class that is the target of the association (relationship). If the collection-valued relationship property is defined using Java generics. Must be specified otherwise.
  • cascade is the operations that must be cascaded to the target of the association
  • fetch defines whether the association should be lazily loaded or must be eagerly fetched.
  • mappedBy defines the field or property that owns the relationship. Required unless the relationship is unidirectional.
  • orphanRemoval defines (Optional) whether to apply the remove operation to entities that have been removed from the relationship and to cascade the remove operation to those entities. orphanRemoval=true will remove the orphans from the many table. If you don't have this parameters and that you change the many value, the old one will remain in the many table.

4 - Snippets

4.1 - Bidirectional with generics

One-to-Many association using generics

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

4.2 - Bidirectional without generics

One-to-Many association without using generics

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

4.3 - Unidirectional using a foreign key mapping

  • In Customer class:
@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

5 - Steps

5.1 - Data Model

The data model is the following:

  • an hello:
HELLO_ID HELLO_DESC
1 Hello Nico !
  • has one or more category
CAT_DESC HELLO_ID
A normal Hello 1

5.1.1 - Database

One Hello has severals hello Categories.

hello_data_model_one_to_many.ddl

5.1.2 - Jpa

With an unidirectional metamodel model.

5.1.2.1 - 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()+
                '}';
    }
}
5.1.2.2 - 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 +
                '}';
    }
}

5.2 - JPA Configuration and Run

With JPA EclipseLink as persistence provider.

5.2.1 - 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>

5.2.2 - 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();
 
    }
 
}

5.2.3 - Output

5.2.3.1 - 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]
5.2.3.2 - System output
HelloWorld{id='1', Description='Hello Nico !', helloCategories={[HelloCategory{desc='A normal Hello'helloWorld=Hello Nico !}]}}
A normal Hello
5.2.3.3 - 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

6 - Documentation / Reference

jpa/one-to-many.txt · Last modified: 2017/09/13 21:21 by gerardnico