Руководство по Hibernate. Виды связей. Set Mapping.

Set (множество) – это коллекция, которая не содержит повторяющихся элементов (только уникальные). Для обеспечения уникальности значений, объекты, содержащиеся в Set, долны реализовывать метода equals() и hashCode(), используя которые, Java-приложение сможет определить являются 2 элемента одинаковыми или нет.

Set связывается (mapped) с помощью элемента <set> и инициализируется с помощью java.util.HashSet.

Другими словами, мы используем Set, если в коллекции будут храниться только уникальные элементы.

Для понимания, как это реализовывается на практике, рассмотрим пример небольшого приложения.

Пример:

Исходный код проекта можно скачать по ЭТОЙ ССЫЛКЕ.
Шаг 1. Создадим две таблицы в нашей БД:

HIBERNATE_DEVELOPERS


CREATE TABLE HIBERNATE_DEVELOPERS(
   ID INT NOT NULL AUTO_INCREMENT,
   FIRST_NAME VARCHAR(50) DEFAULT NULL,
   LAST_NAME VARCHAR(50) DEFAULT NULL,
   SPECIALTY VARCHAR(50) DEFAULT NULL,
   EXPERIENCE INT DEFAULT NULL,
   PRIMARY KEY(ID)
);

HIBERNATE_PROJECTS


create table HIBERNATE_PROJECTS (
   id INT NOT NULL auto_increment,
   PROJECT_NAME VARCHAR(50) default NULL,
   COMPANY VARCHAR(50) default NULL,
   DEVELOPER_ID INT default NULL,
   PRIMARY KEY (id)
);

Мы будем использовать отношение one-to-many между таблицами HIBERNATE_DEVELOPERS и HIBERNATE_PROJECTS.

Шаг 2. Создаём класс Developer.java

Developer.java


package net.proselyte.hibernate.mappings.set;

import java.util.Set;

public class Developer {
    private int id;
    private String firstName;
    private String lastName;
    private String specialty;
    private int experience;
    private Set projects;

    /**
     * Default Constructor
     */
    public Developer() {
    }

    /**
     * Plain constructor
     */
    public Developer(String firstName, String lastName, String specialty, int experience) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.specialty = specialty;
        this.experience = experience;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    public int getExperience() {
        return experience;
    }

    public void setExperience(int experience) {
        this.experience = experience;
    }

    public Set getProjects() {
        return projects;
    }

    public void setProjects(Set projects) {
        this.projects = projects;
    }

    /**
     * toString method (optional)
     */
    @Override
    public String toString() {
        return "Developer:\n" +
                "id: " + id +
                "\nFirst Name: " + firstName + "\n" +
                "Last Name: " + lastName + "\n" +
                "Specialty: " + specialty + "\n" +
                "Experience: " + experience + "\n";
    }
}


Шаг 3. Создём класс Project.java

Project.java


package net.proselyte.hibernate.mappings.set;

public class Project {
    private int id;
    private String projectName;
    private String companyName;

    /**
     * Constructors
     */
    public Project() {
    }

    public Project(String projectName, String companyName) {
        this.projectName = projectName;
        this.companyName = companyName;
    }


    /**
     * Getters and Setters
     */
    public int getId() {
        return id;
    }

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

    public String getProjectName() {
        return projectName;
    }

    public void setProjectName(String projectName) {
        this.projectName = projectName;
    }

    public String getCompanyName() {
        return companyName;
    }


    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    /**
     * Methods equals and hashCode for comparing objects
     */

    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        if (!this.getClass().equals(object.getClass())) {
            return false;
        }

        Project object2 = (Project) object;
        if ((this.id == object2.getId()) && (this.projectName == object2.getProjectName()) && (this.companyName == object2.getCompanyName())) {
            return true;
        }
        return false;
    }

    public int hasCode() {
        int code = 0;
        code = (id + projectName + companyName).hashCode();
        return code;
    }

    @Override
    public String toString() {
        return "Project:\n" +
                "id: " + id +
                "\nProject Name: " + projectName +
                "\nCompany Name: " + companyName + "\n";
    }
}


Шаг 4. Создаём конфигурационный файл hibernate.cfg.xml

hibernate.cfg.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration SYSTEM
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">
            org.hibernate.dialect.MySQLDialect
        </property>
        <property name="hibernate.connection.driver_class">
            com.mysql.jdbc.Driver
        </property>

        <!-- Assume ИМЯ ВАШЕЙ БД is the database name -->
        <property name="hibernate.connection.url">
            jdbc:mysql://localhost/PROSELYTE_TUTORIAL
        </property>
        <property name="hibernate.connection.username">
            ИМЯ ВАШЕЙ БАЗЫ ДАННЫХ
        </property>
        <property name="hibernate.connection.password">
            ВАШ ПАРОЛЬ
        </property>

        <!-- List of XML mapping files -->
        <mapping resource="Developer.hbm.xml"/>

    </session-factory>
</hibernate-configuration>

Шаг 5. Создаём конфигурационный файл Developer.hbm.xml

Developer.hbm.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
    <class name="net.proselyte.hibernate.mappings.set.Developer" table="HIBERNATE_DEVELOPERS">
        <meta attribute="class-description">
            This class contains the developer's detail.
        </meta>
        <id name="id" type="int" column="ID">
            <generator class="native"/>
        </id>
        <set name="projects" cascade="all">
            <key column="DEVELOPER_ID"/>
            <one-to-many class="net.proselyte.hibernate.mappings.set.Project"/>
        </set>
        <property name="firstName" column="FIRST_NAME" type="string"/>
        <property name="lastName" column="LAST_NAME" type="string"/>
        <property name="specialty" column="SPECIALTY" type="string"/>
        <property name="experience" column="EXPERIENCE" type="int"/>
    </class>

    <class name="net.proselyte.hibernate.mappings.set.Project" table="HIBERNATE_PROJECTS">
        <meta attribute="class-description">
            This class contains the projects records.
        </meta>
        <id name="id" type="int" column="id">
            <generator class="native"/>
        </id>
        <property name="projectName" column="PROJECT_NAME" type="string"/>
        <property name="companyName" column="COMPANY" type="string"/>
    </class>
</hibernate-mapping>

Шаг 6. Создаём и запускаем класс DeveloperRunner.java

DeveloperRunner.java


package net.proselyte.hibernate.mappings.set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class DeveloperRunner {
    private static SessionFactory sessionFactory;

    public static void main(String[] args) {
        sessionFactory = new Configuration().configure().buildSessionFactory();

        DeveloperRunner developerRunner = new DeveloperRunner();

        System.out.println("Creating the set of projects.");
        HashSet projects1 = new HashSet();
        projects1.add(new Project("Proselyte Tutorial", "proselyte.net"));
        projects1.add(new Project("SkybleLib", "SkybleSoft"));

        HashSet projects2 = new HashSet();
        projects2.add(new Project("Some Project", "Some Company"));
        projects2.add(new Project("One more Project", "One more Company"));

        System.out.println("Adding developer's records to the DB");

        developerRunner.addDeveloper("Proselyte", "Developer", "Java Developer", 2, projects1);
        developerRunner.addDeveloper("Peter", "UI", "UI Developer", 4, projects2);

        System.out.println("List of developers");
        developerRunner.listDevelopers();

        System.out.println("===================================");
        System.out.println("Updating Proselyte");
        developerRunner.updateDeveloper(31, 3);

        System.out.println("Final list of developers");

        developerRunner.listDevelopers();
        System.out.println("===================================");
        sessionFactory.close();
    }

    public void addDeveloper(String firstName, String lastName, String specialty, int experience, Set projects) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = new Developer(firstName, lastName, specialty, experience);
        developer.setProjects(projects);
        session.save(developer);
        transaction.commit();
        session.close();
    }

    public void listDevelopers() {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        List developers = session.createQuery("FROM Developer").list();
        for (Developer developer : developers) {
            System.out.println(developer);
            Set projects = developer.getProjects();
            for (Project project : projects) {
                System.out.println(project);
            }
            System.out.println("\n================\n");
        }
        session.close();
    }

    public void updateDeveloper(int developerId, int experience) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        developer.setExperience(experience);
        session.update(developer);
        transaction.commit();
        session.close();
    }

    public void removeDeveloper(int developerId) {
        Session session = sessionFactory.openSession();
        Transaction transaction = null;

        transaction = session.beginTransaction();
        Developer developer = (Developer) session.get(Developer.class, developerId);
        session.delete(developer);
        transaction.commit();
        session.close();
    }
}


Если всё было сделано верно, то в результате мы получим следующее:


/usr/lib/jvm/java-8-oracle/bin/java -Didea.launcher.port=7545 -Didea.launcher.bin.path=/home/proselyte/Programming/Soft/IntellijIdea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-8-oracle/jre/lib/management-agent.jar:/usr/lib/jvm/java-8-oracle/jre/lib/plugin.jar:/usr/lib/jvm/java-8-oracle/jre/lib/rt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jsse.jar:/usr/lib/jvm/java-8-oracle/jre/lib/charsets.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jce.jar:/usr/lib/jvm/java-8-oracle/jre/lib/resources.jar:/usr/lib/jvm/java-8-oracle/jre/lib/deploy.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfxswt.jar:/usr/lib/jvm/java-8-oracle/jre/lib/javaws.jar:/usr/lib/jvm/java-8-oracle/jre/lib/jfr.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/dnsns.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunec.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jaccess.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/nashorn.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/zipfs.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/cldrdata.jar:/usr/lib/jvm/java-8-oracle/jre/lib/ext/jfxrt.jar:/home/proselyte/Programming/IdeaProjects/ProselyteTutorials/Hibernate/target/classes:/home/proselyte/.m2/repository/org/springframework/spring-core/4.1.1.RELEASE/spring-core-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.jar:/home/proselyte/.m2/repository/org/springframework/spring-web/4.1.1.RELEASE/spring-web-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-aop/4.1.1.RELEASE/spring-aop-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/aopalliance/aopalliance/1.0/aopalliance-1.0.jar:/home/proselyte/.m2/repository/org/springframework/spring-beans/4.1.1.RELEASE/spring-beans-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-context/4.1.1.RELEASE/spring-context-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/javax/servlet/servlet-api/2.5/servlet-api-2.5.jar:/home/proselyte/.m2/repository/org/springframework/spring-webmvc/4.1.1.RELEASE/spring-webmvc-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-expression/4.1.1.RELEASE/spring-expression-4.1.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-file/4.2.1.RELEASE/spring-integration-file-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/integration/spring-integration-core/4.2.1.RELEASE/spring-integration-core-4.2.1.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-messaging/4.2.2.RELEASE/spring-messaging-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/retry/spring-retry/1.1.2.RELEASE/spring-retry-1.1.2.RELEASE.jar:/home/proselyte/.m2/repository/org/springframework/spring-tx/4.2.2.RELEASE/spring-tx-4.2.2.RELEASE.jar:/home/proselyte/.m2/repository/commons-io/commons-io/2.4/commons-io-2.4.jar:/home/proselyte/.m2/repository/org/hibernate/hibernate-core/5.0.7.Final/hibernate-core-5.0.7.Final.jar:/home/proselyte/.m2/repository/org/jboss/logging/jboss-logging/3.3.0.Final/jboss-logging-3.3.0.Final.jar:/home/proselyte/.m2/repository/org/hibernate/javax/persistence/hibernate-jpa-2.1-api/1.0.0.Final/hibernate-jpa-2.1-api-1.0.0.Final.jar:/home/proselyte/.m2/repository/org/javassist/javassist/3.18.1-GA/javassist-3.18.1-GA.jar:/home/proselyte/.m2/repository/antlr/antlr/2.7.7/antlr-2.7.7.jar:/home/proselyte/.m2/repository/org/apache/geronimo/specs/geronimo-jta_1.1_spec/1.1.1/geronimo-jta_1.1_spec-1.1.1.jar:/home/proselyte/.m2/repository/org/jboss/jandex/2.0.0.Final/jandex-2.0.0.Final.jar:/home/proselyte/.m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar:/home/proselyte/.m2/repository/xml-apis/xml-apis/1.0.b2/xml-apis-1.0.b2.jar:/home/proselyte/.m2/repository/org/hibernate/common/hibernate-commons-annotations/5.0.1.Final/hibernate-commons-annotations-5.0.1.Final.jar:/home/proselyte/.m2/repository/mysql/mysql-connector-java/5.1.38/mysql-connector-java-5.1.38.jar:/home/proselyte/Programming/Soft/IntellijIdea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain net.proselyte.hibernate.mappings.set.DeveloperRunner
Feb 18, 2016 9:33:06 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {5.0.7.Final}
Feb 18, 2016 9:33:06 PM org.hibernate.cfg.Environment 
INFO: HHH000206: hibernate.properties not found
Feb 18, 2016 9:33:06 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Feb 18, 2016 9:33:06 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager 
INFO: HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using Hibernate built-in connection pool (not for production use!)
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: using driver [com.mysql.jdbc.Driver] at URL [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {user=root, password=****}
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
Feb 18, 2016 9:33:07 PM org.hibernate.engine.jdbc.connections.internal.PooledConnections 
INFO: HHH000115: Hibernate connection pool size: 20 (min=1)
Thu Feb 18 21:33:08 EET 2016 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
Feb 18, 2016 9:33:08 PM org.hibernate.dialect.Dialect 
INFO: HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
Creating the set of projects.
Adding developer's records to the DB
List of developers
Feb 18, 2016 9:33:09 PM org.hibernate.hql.internal.QueryTranslatorFactoryInitiator initiateService
INFO: HHH000397: Using ASTQueryTranslatorFactory
Developer:
id: 29
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 33
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 34
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 30
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 35
Project Name: Some Project
Company Name: Some Company

Project:
id: 36
Project Name: One more Project
Company Name: One more Company


================

Developer:
id: 31
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 2

Project:
id: 37
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 38
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 32
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 40
Project Name: One more Project
Company Name: One more Company

Project:
id: 39
Project Name: Some Project
Company Name: Some Company


================

===================================
Updating Proselyte
Final list of developers
Developer:
id: 29
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 33
Project Name: SkybleLib
Company Name: SkybleSoft

Project:
id: 34
Project Name: Proselyte Tutorial
Company Name: proselyte.net


================

Developer:
id: 30
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 35
Project Name: Some Project
Company Name: Some Company

Project:
id: 36
Project Name: One more Project
Company Name: One more Company


================

Developer:
id: 31
First Name: Proselyte
Last Name: Developer
Specialty: Java Developer
Experience: 3

Project:
id: 37
Project Name: Proselyte Tutorial
Company Name: proselyte.net

Project:
id: 38
Project Name: SkybleLib
Company Name: SkybleSoft


================

Developer:
id: 32
First Name: Peter
Last Name: UI
Specialty: UI Developer
Experience: 4

Project:
id: 39
Project Name: Some Project
Company Name: Some Company

Project:
id: 40
Project Name: One more Project
Company Name: One more Company


================

===================================
Feb 18, 2016 9:33:09 PM org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:mysql://localhost/PROSELYTE_TUTORIAL]