Sunday, June 23, 2013

JPA One-to-One mappings tutorial

Most of entities have relationships with other entities.
There are four types of relationships:
  • One-to-One
  • One-to-Many
  • Many-to-One
  • Many-to-Many

Let's look at one-to-one relationship mappings.

This post is available in Russian.

Let's modify the application created in "JPA and Hibernate basics tutorial" post.

Music artist might have only one official website, and a website relate to only one artist. Let's describe such relationship in our application.

Create jpa_one_to_one_mappings_tutorial database with artists and websites tables.
artist_id column of websites table is a foreign key that references artists table.
Add artist and its website data:
CREATE DATABASE jpa_one_to_one_mappings_tutorial;
USE jpa_one_to_one_mappings_tutorial;

CREATE TABLE artists (
artist_id INT PRIMARY KEY,
artist_name VARCHAR(30)
);

CREATE TABLE websites (
website_id INT PRIMARY KEY,
website_url VARCHAR(50),
artist_id INT UNIQUE,
FOREIGN KEY (artist_id) REFERENCES artists (artist_id)
);

INSERT INTO artists VALUES (1, 'Franz Ferdinand');
INSERT INTO websites VALUES (1, 'www.franzferdinand.com', 1);

Unidirectional mapping


If Website entity has a reference to Artist entity, but Artist entity doesn't have a reference to Website entity, then such relationship is unidirectional.

Create Website class with id, url and artist properties.
To create unidirectional one-to-one mapping add @OneToOne and @JoinColumn annotations to artist property. @JoinColumn annotation name element must reference a foreign key column of websites table, e.g. artist_id:
package com.jpa.one.to.one.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "websites")
public class Website {

    @Id
    @Column(name = "website_id")
    private int id;

    @Column(name = "website_url")
    private String url;

    @OneToOne
    @JoinColumn(name = "artist_id")
    Artist artist;

    public Website() {

    }

    public Website(int id, String url) {
        this.id = id;
        this.url = url;
    }

    public int getId() {
        return id;
    }

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

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public Artist getArtist() {
        return artist;
    }

    public void setArtist(Artist artist) {
        this.artist = artist;
    }

    @Override
    public String toString() {
        return "Website{" +
                "id=" + id +
                ", url='" + url + '\'' +               
                '}';
    }
}

Create class WebsiteService:
package com.jpa.one.to.one.mappings.tutorial.service;

import com.jpa.one.to.one.mappings.tutorial.domain.Website;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.List;

public class WebsiteService {

    private EntityManager em;

    public WebsiteService(EntityManager em) {
        this.em = em;
    }

    public Website createWebsite(int id, String url) {
        Website artist = new Website(id, url);
        em.persist(artist);

        return artist;
    }

    public void removeWebsite(int id) {
        Website website = em.find(Website.class, id);

        if (website != null) {
            em.remove(website);
        }
    }

    public Website changeWebsiteUrl(int id, String url) {
        Website website = em.find(Website.class, id);

        if (website != null) {
            website.setUrl(url);
        }

        return website;
    }

    public Website findWebsite(int id) {
        return em.find(Website.class, id);
    }

    public List<Website> findAllWebsites() {
        TypedQuery<Website> query = em.createQuery("SELECT w FROM Website w", Website.class);
        return query.getResultList();
    }
}

Add class Website to persistence unit in persistence.xml file:
<persistence 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_2_0.xsd"
             version="2.0">

    <persistence-unit name="JpaOneToOneMappingsTutorial" transaction-type="RESOURCE_LOCAL">
        <class>com.jpa.one.to.one.mappings.tutorial.domain.Artist</class>
        <class>com.jpa.one.to.one.mappings.tutorial.domain.Website</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url"
                      value="jdbc:mysql://localhost:3306/jpa_one_to_one_mappings_tutorial"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="root"/>
        </properties>
    </persistence-unit>

</persistence>

Add code that creates WebsiteService object, finds a Website entity and prints website and its artist data to main() method of JpaOneToOneMappingsTutorial class:
package com.jpa.one.to.one.mappings.tutorial;

import com.jpa.one.to.one.mappings.tutorial.domain.Website;
import com.jpa.one.to.one.mappings.tutorial.service.WebsiteService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaOneToOneMappingsTutorial {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaOneToOneMappingsTutorial");
        EntityManager em = emf.createEntityManager();
        WebsiteService websiteService = new WebsiteService(em);

        System.out.println("--- Find website ---");
        Website website = websiteService.findWebsite(1);
        System.out.println(String.format("Found website: %s", website));
        System.out.println(String.format("Website artist: %s\n", website.getArtist()));
    }
}

Execute main() method:

Application has printed website and its artist data as expected.

Bidirectional mapping


If Website and Artist entities have references to each other, then such relationship is bidirectional.

To create it:
  • create website property in Artist class
  • add @OneToOne annotation to website property
  • add mappedBy annotation element, which references to Website entity property with @OneToOne and @JoinColumn annotations, e.g. artist:
package com.jpa.one.to.one.mappings.tutorial.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity
@Table(name = "artists")
public class Artist {

    @Id
    @Column(name = "artist_id")
    private int id;

    @Column(name = "artist_name")
    private String name;

    @OneToOne(mappedBy = "artist")
    private Website website;

    public Artist() {

    }

    public Artist(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Website getWebsite() {
        return website;
    }

    public void setWebsite(Website website) {
        this.website = website;
    }

    @Override
    public String toString() {
        return "Artist{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

Add code that creates ArtistService object, finds an Artist entity and prints artist and its website data to main() method of JpaOneToOneMappingsTutorial class:
package com.jpa.one.to.one.mappings.tutorial;

import com.jpa.one.to.one.mappings.tutorial.domain.Artist;
import com.jpa.one.to.one.mappings.tutorial.domain.Website;
import com.jpa.one.to.one.mappings.tutorial.service.ArtistService;
import com.jpa.one.to.one.mappings.tutorial.service.WebsiteService;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class JpaOneToOneMappingsTutorial {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("JpaOneToOneMappingsTutorial");
        EntityManager em = emf.createEntityManager();        
        WebsiteService websiteService = new WebsiteService(em);
        ArtistService artistService = new ArtistService(em);

        System.out.println("--- Find website ---");
        Website website = websiteService.findWebsite(1);
        System.out.println(String.format("Found website: %s", website));
        System.out.println(String.format("Website artist: %s\n", website.getArtist()));

        System.out.println("--- Find artist ---");
        Artist artist = artistService.findArtist(1);
        System.out.println(String.format("Found artist: %s", artist));
        System.out.println(String.format("Artist website: %s\n", artist.getWebsite()));
    }
}

Execute main() method:

Application has printed artist and its website data, which means we have successfully created unidirectional and bidirectional one-to-one mappings during application development.

Folder structure:




Application source code is available at https://code.google.com/p/jpa-one-to-one-mappings-tutorial/.

Recommended posts:

5 comments:

  1. In database this is relation one to many you forget unique index , this is not one to one ralation.In example you insert to website table in artis id 1.

    ReplyDelete
    Replies
    1. You are right, Piotr. Added "UNIQUE" for "artist_id" column from "websites" table

      Delete
  2. How to use OntToOne but with LAZY LOAD and save entity

    ReplyDelete